Commit aab6cde9 authored by David Reid's avatar David Reid

Lots of work on improving synchronization and thread-safety:

  - Use binary semaphores as the synchronization primitive for the worker thread
  - Generalize the worker thread so as to avoid having different worker thread
    for each backend.
  - Make state changes atomic

General improvements bug fixing:
  - Fix an error where the DirectSound backend would immediately send the
    application a bunch of invalid audio data.
  - Have the ALSA backend request more audio data only immediately before it's
    ready to write it to the device.
  - General improvements on documentation.
  - Update readme.
parent d2920b18
...@@ -36,7 +36,7 @@ int main() ...@@ -36,7 +36,7 @@ int main()
mal_uint32 fragmentCount = 2; mal_uint32 fragmentCount = 2;
mal_device playbackDevice; mal_device playbackDevice;
if (mal_device_init(&playbackDevice, mal_device_type_playback, NULL, mal_format_f32, channels, sampleRate, fragmentSizeInFrames, fragmentCount) != MAL_SUCCESS) { if (mal_device_init_async(&playbackDevice, mal_device_type_playback, NULL, mal_format_f32, channels, sampleRate, fragmentSizeInFrames, fragmentCount) != MAL_SUCCESS) {
return -1; return -1;
} }
...@@ -49,42 +49,4 @@ int main() ...@@ -49,42 +49,4 @@ int main()
mal_device_uninit(&playbackDevice); mal_device_uninit(&playbackDevice);
return 0; return 0;
} }
```
Synchronous API
---------------
In synchronous mode, the application submits audio data to the device using APIs which block until
the device is ready to receive it.
```
int main()
{
mal_uint32 channels = 2;
mal_uint32 sampleRate = 44100;
mal_uint32 fragmentSizeInFrames = 512;
mal_uint32 fragmentCount = 2;
mal_device playbackDevice;
if (mal_device_init_synchronous(&playbackDevice, mal_device_type_playback, NULL, mal_format_f32, channels, sampleRate, fragmentSizeInFrames, fragmentCount) != MAL_SUCCESS) {
return -1;
}
mal_device_start(&playbackDevice);
for (;;) {
float pSamples[512];
mal_uint32 samplesToWrite = drwav_read_f32(&wav, fragmentSizeInFrames * fragmentCount, pSamples);
if (samplesToWrite == 0) {
break;
}
mal_uint32 samplesWritten = mal_device_write(&playbackDevice, samplesToWrite, pSamples);
if (samplesWritte != samplesToWrite) {
break;
}
}
mal_device_uninit(&playbackDevice);
return 0;
}
``` ```
\ No newline at end of file
...@@ -13,8 +13,18 @@ ...@@ -13,8 +13,18 @@
// ===== // =====
// - This library uses an asynchronous API delivering and requesting audio data. Each device will have // - This library uses an asynchronous API delivering and requesting audio data. Each device will have
// it's own worker thread which is managed by the library. // it's own worker thread which is managed by the library.
// - This is not currently thread-safe, but can still be used from multiple threads if you do your own // - If mal_device_init() is called with a device that's not aligned to the platform's natural alignment
// synchronization. // boundary (4 bytes on 32-bit, 8 bytes on 64-bit), it will _not_ be thread-safe. The reason for this
// is that it depends on members of mal_device being correctly aligned for atomic assignments and bit
// manipulation.
//
//
// BACKEND NUANCES
// ===============
// - For playback devices, the ALSA backend will pre-fill every fragment with sample data. The DirectSound
// backend only pre-fills the first fragment.
// - DirectSound has _bad_ latency compared to other backends. In my testing, a fragment size of 1024 frames
// is too small, but a size of 2048 seems to work.
#ifndef dr_mal_h #ifndef dr_mal_h
#define dr_mal_h #define dr_mal_h
...@@ -44,6 +54,7 @@ extern "C" { ...@@ -44,6 +54,7 @@ extern "C" {
#define MAL_ENABLE_NULL #define MAL_ENABLE_NULL
#endif #endif
#if defined(_MSC_VER) && _MSC_VER < 1600 #if defined(_MSC_VER) && _MSC_VER < 1600
typedef signed char mal_int8; typedef signed char mal_int8;
typedef unsigned char mal_uint8; typedef unsigned char mal_uint8;
...@@ -74,10 +85,18 @@ typedef void* mal_ptr; ...@@ -74,10 +85,18 @@ typedef void* mal_ptr;
#ifdef MAL_WIN32 #ifdef MAL_WIN32
typedef mal_handle mal_thread; typedef mal_handle mal_thread;
typedef mal_handle mal_semaphore; typedef mal_handle mal_event;
typedef mal_handle mal_semaphore;
#else #else
typedef pthread_t mal_thread; typedef pthread_t mal_thread;
typedef sem_t mal_semaphore; typedef sem_t mal_semaphore;
typedef struct
{
pthread_mutex_t mutex;
pthread_cond_t condition;
mal_uint32 value;
} mal_event;
#endif #endif
#ifdef MAL_ENABLE_DSOUND #ifdef MAL_ENABLE_DSOUND
...@@ -85,15 +104,22 @@ typedef void* mal_ptr; ...@@ -85,15 +104,22 @@ typedef void* mal_ptr;
#endif #endif
typedef int mal_result; typedef int mal_result;
#define MAL_SUCCESS 0 #define MAL_SUCCESS 0
#define MAL_UNKNOWN_ERROR -1 #define MAL_ERROR -1
#define MAL_INVALID_ARGS -2 #define MAL_INVALID_ARGS -2
#define MAL_OUT_OF_MEMORY -3 #define MAL_OUT_OF_MEMORY -3
#define MAL_NO_BACKEND -16 #define MAL_NO_BACKEND -16
#define MAL_DEVICE_ALREADY_STARTED -17 #define MAL_DEVICE_BUSY -32 // The device is already in the middle of something.
#define MAL_DEVICE_ALREADY_STOPPED -18 #define MAL_DEVICE_NOT_INITIALIZED -33 // Trying to do something on an uninitialized device.
#define MAL_FAILED_TO_INIT_BACKEND -19 #define MAL_DEVICE_ALREADY_STARTED -17
#define MAL_FORMAT_NOT_SUPPORTED -20 #define MAL_DEVICE_ALREADY_STARTING -18
#define MAL_DEVICE_ALREADY_STOPPED -19
#define MAL_DEVICE_ALREADY_STOPPING -20
#define MAL_FAILED_TO_INIT_BACKEND -21
#define MAL_FORMAT_NOT_SUPPORTED -22
#define MAL_FAILED_TO_READ_DATA_FROM_CLIENT -23
#define MAL_FAILED_TO_START_BACKEND_DEVICE -24
#define MAL_FAILED_TO_STOP_BACKEND_DEVICE -25
typedef struct mal_device mal_device; typedef struct mal_device mal_device;
...@@ -147,11 +173,16 @@ struct mal_device ...@@ -147,11 +173,16 @@ struct mal_device
mal_uint32 sampleRate; mal_uint32 sampleRate;
mal_uint32 fragmentSizeInFrames; mal_uint32 fragmentSizeInFrames;
mal_uint32 fragmentCount; mal_uint32 fragmentCount;
mal_uint32 flags; mal_uint32 state;
mal_recv_proc onRecv; mal_recv_proc onRecv;
mal_send_proc onSend; mal_send_proc onSend;
void* pUserData; // Application defined data. void* pUserData; // Application defined data.
mal_event wakeupEvent;
mal_event startEvent;
mal_event stopEvent;
mal_thread thread;
mal_result workResult; // This is set by the worker thread after it's finished doing a job.
union union
{ {
#ifdef MAL_ENABLE_DSOUND #ifdef MAL_ENABLE_DSOUND
...@@ -166,8 +197,7 @@ struct mal_device ...@@ -166,8 +197,7 @@ struct mal_device
/*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_FRAGMENTS_DSOUND]; // One event handle for each fragment.
/*HANDLE*/ mal_handle hStopEvent; /*HANDLE*/ mal_handle hStopEvent;
mal_thread thread; mal_uint32 ignoredFragmentCounter; // <-- This is used for a cheap hack to skip over some initial notifications when the device is first played.
mal_semaphore semaphore; // <-- This is used to wake up the worker thread.
} dsound; } dsound;
#endif #endif
...@@ -175,8 +205,8 @@ struct mal_device ...@@ -175,8 +205,8 @@ struct mal_device
struct struct
{ {
/*snd_pcm_t**/mal_ptr pPCM; /*snd_pcm_t**/mal_ptr pPCM;
mal_thread thread; mal_bool32 isUsingMMap;
mal_semaphore semaphore; // <-- This is used to wake up the thread. mal_bool32 breakFromMainLoop;
void* pIntermediaryBuffer; void* pIntermediaryBuffer;
} alsa; } alsa;
#endif #endif
...@@ -196,11 +226,11 @@ struct mal_device ...@@ -196,11 +226,11 @@ struct mal_device
// This API uses an application-defined buffer for output. This is thread-safe so long as the // This API uses an application-defined buffer for output. This is thread-safe so long as the
// application ensures mutal exclusion to the output buffer at their level. // application ensures mutal exclusion to the output buffer at their level.
// //
// Efficiency: SLOW // Efficiency: LOW
// This API dynamically links to backend DLLs/SOs (such as dsound.dll). // This API dynamically links to backend DLLs/SOs (such as dsound.dll).
mal_result mal_enumerate_devices(mal_device_type type, mal_uint32* pCount, mal_device_info* pInfo); mal_result mal_enumerate_devices(mal_device_type type, mal_uint32* pCount, mal_device_info* pInfo);
// Initializes a device. // Initializes a device in asynchronous mode.
// //
// The device ID (pDeviceID) can be null, in which case the default device is used. Otherwise, you // The device ID (pDeviceID) can be null, in which case the default device is used. Otherwise, you
// can retrieve the ID by calling mal_enumerate_devices() and retrieve the ID from the returned // can retrieve the ID by calling mal_enumerate_devices() and retrieve the ID from the returned
...@@ -214,10 +244,10 @@ mal_result mal_enumerate_devices(mal_device_type type, mal_uint32* pCount, mal_d ...@@ -214,10 +244,10 @@ mal_result mal_enumerate_devices(mal_device_type type, mal_uint32* pCount, mal_d
// This API is thread safe so long as the application does not try to use the device object before // This API is thread safe so long as the application does not try to use the device object before
// this call has returned. // this call has returned.
// //
// Efficiency: SLOW // 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 fact that it's an initialization API. // due to the fact that it's 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_result mal_device_init_async(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);
// Uninitializes a device. // Uninitializes a device.
// //
...@@ -228,7 +258,7 @@ mal_result mal_device_init(mal_device* pDevice, mal_device_type type, mal_device ...@@ -228,7 +258,7 @@ mal_result mal_device_init(mal_device* pDevice, mal_device_type type, mal_device
// This API shouldn't crash in a multi-threaded environment, but results are undefined if an application // This API shouldn't crash in a multi-threaded environment, but results are undefined if an application
// attempts to do something with the device at the same time as uninitializing. // attempts to do something with the device at the same time as uninitializing.
// //
// Efficiency: SLOW // Efficiency: LOW
// This will stop the device with mal_device_stop() which is a slow, synchronized call. It also needs // This will stop the device with mal_device_stop() which is a slow, synchronized call. It also needs
// to destroy internal objects like the backend-specific objects and the background thread. // to destroy internal objects like the backend-specific objects and the background thread.
void mal_device_uninit(mal_device* pDevice); void mal_device_uninit(mal_device* pDevice);
...@@ -238,7 +268,7 @@ void mal_device_uninit(mal_device* pDevice); ...@@ -238,7 +268,7 @@ void mal_device_uninit(mal_device* pDevice);
// Thread Safety: SAFE // Thread Safety: SAFE
// This API is implemented as a simple atomic assignment. // This API is implemented as a simple atomic assignment.
// //
// Efficiency: FAST // Efficiency: HIGH
// This is just an atomic assignment. // This is just an atomic assignment.
void mal_device_set_recv_callback(mal_device* pDevice, mal_recv_proc proc); void mal_device_set_recv_callback(mal_device* pDevice, mal_recv_proc proc);
...@@ -247,24 +277,43 @@ void mal_device_set_recv_callback(mal_device* pDevice, mal_recv_proc proc); ...@@ -247,24 +277,43 @@ void mal_device_set_recv_callback(mal_device* pDevice, mal_recv_proc proc);
// Thread Safety: SAFE // Thread Safety: SAFE
// This API is implemented as a simple atomic assignment. // This API is implemented as a simple atomic assignment.
// //
// Efficiency: FAST // Efficiency: HIGH
// This is just an atomic assignment. // This is just an atomic assignment.
void mal_device_set_send_callback(mal_device* pDevice, mal_send_proc proc); void mal_device_set_send_callback(mal_device* pDevice, mal_send_proc proc);
// Activates the device. For playback devices this begins playback. For recording devices it begins // Activates the device. For playback devices this begins playback. For recording devices it begins
// recording. // recording.
// //
// For a playback device, this will retrieve an initial chunk of audio data from the client before
// returning. This reason for this is to ensure there is valid audio data in the buffer, which needs
// to be done _before_ the device starts playing back audio.
//
// Return Value:
// MAL_SUCCESS if successful.
//
// MAL_DEVICE_BUSY
// The device is in the process of stopping. This will only happen if mal_device_start() and
// mal_device_stop() is called simultaneous on separate threads. This will never be returned in
// single-threaded applications.
//
// MAL_DEVICE_ALREADY_STARTING
// The device is already in the process of starting. This will never be returned in single-threaded
// applications.
//
// MAL_DEVICE_ALREADY_STARTED
// The device is already started.
//
// Thread Safety: SAFE // Thread Safety: SAFE
// //
// Efficiency: SLOW // Efficiency: LOW
// This API needs to wait on the worker thread via a semaphore. // This API waits until the backend device has been closed for real by the worker thread.
mal_result mal_device_start(mal_device* pDevice); mal_result mal_device_start(mal_device* pDevice);
// Puts the device to sleep, but does not uninitialize it. Use mal_device_start() to start it up again. // Puts the device to sleep, but does not uninitialize it. Use mal_device_start() to start it up again.
// //
// Thread Safety: SAFE // Thread Safety: SAFE
// //
// Efficiency: SLOW // Efficiency: LOW
// This API needs to wait on the worker thread to stop the backend device properly before returning. // This API needs to wait on the worker thread to stop the backend device properly before returning.
mal_result mal_device_stop(mal_device* pDevice); mal_result mal_device_stop(mal_device* pDevice);
...@@ -274,7 +323,7 @@ mal_result mal_device_stop(mal_device* pDevice); ...@@ -274,7 +323,7 @@ mal_result mal_device_stop(mal_device* pDevice);
// If another thread calls mal_device_start() or mal_device_stop() at this same time as this function // If another thread calls mal_device_start() or mal_device_stop() at this same time as this function
// is called, there's a very small chance the return value will out of sync. // is called, there's a very small chance the return value will out of sync.
// //
// Efficiency: FAST // Efficiency: HIGH
// This is implemented with a simple accessor. // This is implemented with a simple accessor.
mal_bool32 mal_device_is_started(mal_device* pDevice); mal_bool32 mal_device_is_started(mal_device* pDevice);
...@@ -283,7 +332,7 @@ mal_bool32 mal_device_is_started(mal_device* pDevice); ...@@ -283,7 +332,7 @@ mal_bool32 mal_device_is_started(mal_device* pDevice);
// 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: FAST // 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_fragment_size_in_bytes(mal_device* pDevice);
...@@ -292,7 +341,7 @@ mal_uint32 mal_device_get_fragment_size_in_bytes(mal_device* pDevice); ...@@ -292,7 +341,7 @@ mal_uint32 mal_device_get_fragment_size_in_bytes(mal_device* pDevice);
// Thread Safety: SAFE // Thread Safety: SAFE
// This is API is pure. // This is API is pure.
// //
// Efficiency: FAST // Efficiency: HIGH
// This is implemented with a lookup table. // This is implemented with a lookup table.
mal_uint32 mal_get_sample_size_in_bytes(mal_format format); mal_uint32 mal_get_sample_size_in_bytes(mal_format format);
...@@ -327,6 +376,33 @@ mal_uint32 mal_get_sample_size_in_bytes(mal_format format); ...@@ -327,6 +376,33 @@ mal_uint32 mal_get_sample_size_in_bytes(mal_format format);
#include <stdio.h> // For printf() debugging. TODO: Delete this and replace with a proper logging system. #include <stdio.h> // For printf() debugging. TODO: Delete this and replace with a proper logging system.
#ifdef _WIN32
#ifdef _WIN64
#define MAL_64BIT
#else
#define MAL_32BIT
#endif
#endif
#ifdef __GNUC__
#ifdef __LP64__
#define MAL_64BIT
#else
#define MAL_32BIT
#endif
#endif
#if !defined(MAL_64BIT) && !defined(MAL_32BIT)
#include <stdint.h>
#if INTPTR_MAX == INT64_MAX
#define MAL_64BIT
#else
#define MAL_32BIT
#endif
#endif
#ifdef MAL_WIN32 #ifdef MAL_WIN32
#define MAL_THREADCALL WINAPI #define MAL_THREADCALL WINAPI
typedef mal_uint32 mal_thread_result; typedef mal_uint32 mal_thread_result;
...@@ -336,12 +412,11 @@ mal_uint32 mal_get_sample_size_in_bytes(mal_format format); ...@@ -336,12 +412,11 @@ mal_uint32 mal_get_sample_size_in_bytes(mal_format format);
#endif #endif
typedef mal_thread_result (MAL_THREADCALL * mal_thread_entry_proc)(void* pData); typedef mal_thread_result (MAL_THREADCALL * mal_thread_entry_proc)(void* pData);
#define MAL_STATE_UNINITIALIZED 0
#define MAL_FLAG_INITIALIZED (1 << 0) #define MAL_STATE_STOPPED 1 // The device's default state after initialization.
#define MAL_FLAG_AWAKE (1 << 1) #define MAL_STATE_STARTED 2 // The worker thread is in it's main loop waiting for the driver to request or deliver audio data.
#define MAL_FLAG_STARTED (1 << 2) // Whether or not the device is currently awake and running. #define MAL_STATE_STARTING 3 // Transitioning from a stopped state to started.
#define MAL_FLAG_TERMINATING (1 << 3) // Used for thread management. #define MAL_STATE_STOPPING 4 // Transitioning from a started state to stopped.
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// //
...@@ -437,12 +512,36 @@ static unsigned int mal_next_power_of_2(unsigned int x) ...@@ -437,12 +512,36 @@ static unsigned int mal_next_power_of_2(unsigned int x)
} }
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// //
// Threading // Atomics
// //
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
#if defined(_WIN32) && defined(_MSC_VER)
#define mal_memory_barrier() MemoryBarrier()
#define mal_atomic_exchange_32(a, b) InterlockedExchange((LONG*)a, (LONG)b)
#define mal_atomic_exchange_64(a, b) InterlockedExchange64((LONGLONG*)a, (LONGLONG)b)
#else
#define mal_memory_barrier() __sync_synchronize()
#define mal_atomic_exchange_32(a, b) (void)__sync_lock_test_and_set(a, b); __sync_synchronize()
#define mal_atomic_exchange_64(a, b) (void)__sync_lock_test_and_set(a, b); __sync_synchronize()
#endif
#ifdef MAL_64BIT
#define mal_atomic_exchange_ptr mal_atomic_exchange_64
#endif
#ifdef MAL_32BIT
#define mal_atomic_exchange_ptr mal_atomic_exchange_32
#endif
///////////////////////////////////////////////////////////////////////////////
//
// Threading
//
///////////////////////////////////////////////////////////////////////////////
#ifdef MAL_WIN32 #ifdef MAL_WIN32
mal_bool32 mal_thread_create__win32(mal_thread* pThread, mal_thread_entry_proc entryProc, void* pData) mal_bool32 mal_thread_create__win32(mal_thread* pThread, mal_thread_entry_proc entryProc, void* pData)
{ {
...@@ -460,29 +559,29 @@ void mal_thread_wait__win32(mal_thread* pThread) ...@@ -460,29 +559,29 @@ void mal_thread_wait__win32(mal_thread* pThread)
} }
mal_bool32 mal_semaphore_create__win32(mal_semaphore* pSemaphore, int initialValue) mal_bool32 mal_event_create__win32(mal_event* pEvent)
{ {
*pSemaphore = CreateSemaphoreA(NULL, initialValue, LONG_MAX, NULL); *pEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
if (*pSemaphore == NULL) { if (*pEvent == NULL) {
return MAL_FALSE; return MAL_FALSE;
} }
return MAL_TRUE; return MAL_TRUE;
} }
void mal_semaphore_delete__win32(mal_semaphore* pSemaphore) void mal_event_delete__win32(mal_event* pEvent)
{ {
CloseHandle(*pSemaphore); CloseHandle(*pEvent);
} }
mal_bool32 mal_semaphore_wait__win32(mal_semaphore* pSemaphore) mal_bool32 mal_event_wait__win32(mal_event* pEvent)
{ {
return WaitForSingleObject(*pSemaphore, INFINITE) == WAIT_OBJECT_0; return WaitForSingleObject(*pEvent, INFINITE) == WAIT_OBJECT_0;
} }
mal_bool32 mal_semaphore_release__win32(mal_semaphore* pSemaphore) mal_bool32 mal_event_signal__win32(mal_event* pEvent)
{ {
return ReleaseSemaphore(*pSemaphore, 1, NULL) != 0; return SetEvent(*pEvent);
} }
#endif #endif
...@@ -499,24 +598,51 @@ void mal_thread_wait__posix(mal_thread* pThread) ...@@ -499,24 +598,51 @@ void mal_thread_wait__posix(mal_thread* pThread)
} }
mal_bool32 mal_semaphore_create__posix(mal_semaphore* pSemaphore, int initialValue) mal_bool32 mal_event_create__posix(mal_event* pEvent)
{ {
return sem_init(pSemaphore, 0, (unsigned int)initialValue) != -1; if (pthread_mutex_init(&pEvent->mutex, NULL) != 0) {
return MAL_FALSE;
}
if (pthread_cond_init(&pEvent->condition, NULL) != 0) {
return MAL_FALSE;
}
pEvent->value = 0;
return MAL_TRUE;
} }
void mal_semaphore_delete__posix(mal_semaphore* pSemaphore) void mal_event_delete__posix(mal_event* pEvent)
{ {
sem_close(pSemaphore); pthread_cond_destroy(&pEvent->condition);
pthread_mutex_destroy(&pEvent->mutex);
} }
mal_bool32 mal_semaphore_wait__posix(mal_semaphore* pSemaphore) mal_bool32 mal_event_wait__posix(mal_event* pEvent)
{ {
return sem_wait(pSemaphore) != -1; pthread_mutex_lock(&pEvent->mutex);
{
while (pEvent->value == 0) {
pthread_cond_wait(&pEvent->condition, &pEvent->mutex);
}
pEvent->value = 0; // Auto-reset.
}
pthread_mutex_unlock(&pEvent->mutex);
return MAL_TRUE;
} }
mal_bool32 mal_semaphore_release__posix(mal_semaphore* pSemaphore) mal_bool32 mal_event_signal__posix(mal_event* pEvent)
{ {
return sem_post(pSemaphore) != -1; pthread_mutex_lock(&pEvent->mutex);
{
pEvent->value = 1;
pthread_cond_signal(&pEvent->condition);
}
pthread_mutex_unlock(&pEvent->mutex);
return MAL_TRUE;
} }
#endif #endif
...@@ -547,6 +673,60 @@ void mal_thread_wait(mal_thread* pThread) ...@@ -547,6 +673,60 @@ void mal_thread_wait(mal_thread* pThread)
} }
mal_bool32 mal_event_create(mal_event* pEvent)
{
if (pEvent == NULL) return MAL_FALSE;
#ifdef MAL_WIN32
return mal_event_create__win32(pEvent);
#endif
#ifdef MAL_POSIX
return mal_event_create__posix(pEvent);
#endif
}
void mal_event_delete(mal_event* pEvent)
{
if (pEvent == NULL) return;
#ifdef MAL_WIN32
mal_event_delete__win32(pEvent);
#endif
#ifdef MAL_POSIX
mal_event_delete__posix(pEvent);
#endif
}
mal_bool32 mal_event_wait(mal_event* pEvent)
{
if (pEvent == NULL) return MAL_FALSE;
#ifdef MAL_WIN32
return mal_event_wait__win32(pEvent);
#endif
#ifdef MAL_POSIX
return mal_event_wait__posix(pEvent);
#endif
}
mal_bool32 mal_event_signal(mal_event* pEvent)
{
if (pEvent == NULL) return MAL_FALSE;
#ifdef MAL_WIN32
return mal_event_signal__win32(pEvent);
#endif
#ifdef MAL_POSIX
return mal_event_signal__posix(pEvent);
#endif
}
#if 0
mal_bool32 mal_semaphore_create(mal_semaphore* pSemaphore, int initialValue) mal_bool32 mal_semaphore_create(mal_semaphore* pSemaphore, int initialValue)
{ {
if (pSemaphore == NULL) return MAL_FALSE; if (pSemaphore == NULL) return MAL_FALSE;
...@@ -598,12 +778,13 @@ mal_bool32 mal_semaphore_release(mal_semaphore* pSemaphore) ...@@ -598,12 +778,13 @@ mal_bool32 mal_semaphore_release(mal_semaphore* pSemaphore)
return mal_semaphore_release__posix(pSemaphore); return mal_semaphore_release__posix(pSemaphore);
#endif #endif
} }
#endif
// A helper function for reading sample data from the client. Returns the number of samples read from the client. Remaining samples // A helper function for reading sample data from the client. Returns the number of samples read from the client. Remaining samples
// are filled with silence. // are filled with silence.
static inline mal_uint32 mal_device__read_fragment_from_client(mal_device* pDevice, mal_uint32 sampleCount, void* pSamples) static inline mal_uint32 mal_device__read_samples_from_client(mal_device* pDevice, mal_uint32 sampleCount, void* pSamples)
{ {
mal_assert(pDevice != NULL); mal_assert(pDevice != NULL);
mal_assert(sampleCount > 0); mal_assert(sampleCount > 0);
...@@ -623,7 +804,7 @@ static inline mal_uint32 mal_device__read_fragment_from_client(mal_device* pDevi ...@@ -623,7 +804,7 @@ static inline mal_uint32 mal_device__read_fragment_from_client(mal_device* pDevi
} }
// A helper for sending sample data to the client. // A helper for sending sample data to the client.
static inline void mal_device__send_fragment_to_client(mal_device* pDevice, mal_uint32 sampleCount, const void* pSamples) static inline void mal_device__send_samples_to_client(mal_device* pDevice, mal_uint32 sampleCount, const void* pSamples)
{ {
mal_assert(pDevice != NULL); mal_assert(pDevice != NULL);
mal_assert(sampleCount > 0); mal_assert(sampleCount > 0);
...@@ -634,6 +815,18 @@ static inline void mal_device__send_fragment_to_client(mal_device* pDevice, mal_ ...@@ -634,6 +815,18 @@ static inline void mal_device__send_fragment_to_client(mal_device* pDevice, mal_
} }
} }
// A helper for changing the state of the device.
static inline void mal_device__set_state(mal_device* pDevice, mal_uint32 newState)
{
mal_atomic_exchange_32(&pDevice->state, newState);
}
// A helper for getting the state of the device.
static inline mal_uint32 mal_device__get_state(mal_device* pDevice)
{
return pDevice->state;
}
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// //
...@@ -673,18 +866,32 @@ mal_result mal_device_init__null(mal_device* pDevice, mal_device_type type, mal_ ...@@ -673,18 +866,32 @@ mal_result mal_device_init__null(mal_device* pDevice, mal_device_type type, mal_
return MAL_NO_BACKEND; return MAL_NO_BACKEND;
} }
mal_result mal_device_start__null(mal_device* pDevice) static mal_result mal_device__start_backend__null(mal_device* pDevice)
{ {
mal_assert(pDevice != NULL); mal_assert(pDevice != NULL);
return MAL_UNKNOWN_ERROR; return MAL_ERROR;
} }
mal_result mal_device_stop__null(mal_device* pDevice) static mal_result mal_device__stop_backend__null(mal_device* pDevice)
{ {
mal_assert(pDevice != NULL); mal_assert(pDevice != NULL);
return MAL_UNKNOWN_ERROR; return MAL_ERROR;
}
static mal_result mal_device__break_main_loop__null(mal_device* pDevice)
{
mal_assert(pDevice != NULL);
return MAL_ERROR;
}
static mal_result mal_device__main_loop__null(mal_device* pDevice)
{
mal_assert(pDevice != NULL);
return MAL_ERROR;
} }
#endif #endif
...@@ -718,9 +925,6 @@ typedef HRESULT (WINAPI * mal_DirectSoundEnumerateAProc)(LPDSENUMCALLBACKA pDSEn ...@@ -718,9 +925,6 @@ typedef HRESULT (WINAPI * mal_DirectSoundEnumerateAProc)(LPDSENUMCALLBACKA pDSEn
typedef HRESULT (WINAPI * mal_DirectSoundCaptureCreate8Proc)(LPCGUID pcGuidDevice, LPDIRECTSOUNDCAPTURE8 *ppDSC8, LPUNKNOWN pUnkOuter); typedef HRESULT (WINAPI * mal_DirectSoundCaptureCreate8Proc)(LPCGUID pcGuidDevice, LPDIRECTSOUNDCAPTURE8 *ppDSC8, LPUNKNOWN pUnkOuter);
typedef HRESULT (WINAPI * mal_DirectSoundCaptureEnumerateAProc)(LPDSENUMCALLBACKA pDSEnumCallback, LPVOID pContext); typedef HRESULT (WINAPI * mal_DirectSoundCaptureEnumerateAProc)(LPDSENUMCALLBACKA pDSEnumCallback, LPVOID pContext);
#define MAL_FLAG_DSOUND_HAS_SEMAPHORE (1 << 16) // Used for cleanup.
#define MAL_FLAG_DSOUND_HAS_THREAD (1 << 17) // Used for cleanup.
static HMODULE mal_open_dsound_dll() static HMODULE mal_open_dsound_dll()
{ {
return LoadLibraryW(L"dsound.dll"); return LoadLibraryW(L"dsound.dll");
...@@ -731,117 +935,6 @@ static void mal_close_dsound_dll(HMODULE hModule) ...@@ -731,117 +935,6 @@ static void mal_close_dsound_dll(HMODULE hModule)
FreeLibrary(hModule); FreeLibrary(hModule);
} }
static mal_result mal_device__read_fragment_from_client__dsound(mal_device* pDevice, mal_uint32 fragmentIndex)
{
mal_assert(pDevice != NULL);
DWORD fragmentSizeInBytes = pDevice->fragmentSizeInFrames * pDevice->channels * mal_get_sample_size_in_bytes(pDevice->format);
DWORD offset = fragmentIndex * fragmentSizeInBytes;
void* pLockPtr;
DWORD lockSize;
if (FAILED(IDirectSoundBuffer_Lock((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackBuffer, offset, fragmentSizeInBytes, &pLockPtr, &lockSize, NULL, NULL, 0))) {
return MAL_UNKNOWN_ERROR;
}
mal_device__read_fragment_from_client(pDevice, pDevice->fragmentSizeInFrames * pDevice->channels, pLockPtr);
IDirectSoundBuffer_Unlock((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackBuffer, pLockPtr, lockSize, NULL, 0);
return MAL_SUCCESS;
}
static mal_result mal_device__send_fragment_to_client__dsound(mal_device* pDevice, mal_uint32 fragmentIndex)
{
mal_assert(pDevice != NULL);
DWORD fragmentSizeInBytes = pDevice->fragmentSizeInFrames * pDevice->channels * mal_get_sample_size_in_bytes(pDevice->format);
DWORD offset = fragmentIndex * fragmentSizeInBytes;
void* pLockPtr;
DWORD lockSize;
if (FAILED(IDirectSoundCaptureBuffer_Lock((LPDIRECTSOUNDCAPTUREBUFFER8)pDevice->dsound.pCaptureBuffer, offset, fragmentSizeInBytes, &pLockPtr, &lockSize, NULL, NULL, 0))) {
return MAL_UNKNOWN_ERROR;
}
mal_device__send_fragment_to_client(pDevice, pDevice->fragmentSizeInFrames * pDevice->channels, pLockPtr);
IDirectSoundCaptureBuffer_Unlock((LPDIRECTSOUNDCAPTUREBUFFER)pDevice->dsound.pCaptureBuffer, pLockPtr, lockSize, NULL, 0);
return MAL_SUCCESS;
}
mal_thread_result MAL_THREADCALL mal_worker_thread__dsound(void* pData)
{
mal_device* pDevice = (mal_device*)pData;
mal_assert(pDevice != NULL);
for (;;) {
mal_semaphore_wait(&pDevice->dsound.semaphore);
// Just break if we're terminating.
if (pDevice->flags & MAL_FLAG_TERMINATING) {
break;
}
// Continue if the device has been stopped.
if (!mal_device_is_started(pDevice)) {
if (pDevice->type == mal_device_type_playback) {
IDirectSoundBuffer_Stop((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackBuffer);
IDirectSoundBuffer_SetCurrentPosition((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackBuffer, 0);
} else {
IDirectSoundCaptureBuffer_Stop((LPDIRECTSOUNDCAPTUREBUFFER)pDevice->dsound.pCaptureBuffer);
}
continue;
}
// Getting here means we just started the device and we need to wait for the device to
// either deliver us data (recording) or request more data (playback).
if (pDevice->type == mal_device_type_playback) {
// Before playing anything we need to grab an initial fragment of sample data from the client.
if (mal_device__read_fragment_from_client__dsound(pDevice, 0) != MAL_SUCCESS) {
continue; // Just cancel playback and go back to the start of the loop.
}
IDirectSoundBuffer_Play((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackBuffer, 0, 0, DSBPLAY_LOOPING);
} else {
IDirectSoundCaptureBuffer8_Start((LPDIRECTSOUNDCAPTUREBUFFER8)pDevice->dsound.pCaptureBuffer, DSCBSTART_LOOPING);
}
for (;;) {
// Wait for a notification. Notifications are tied to fragments.
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);
eventHandles[eventCount-1] = pDevice->dsound.hStopEvent;
DWORD rc = WaitForMultipleObjects(eventCount, eventHandles, FALSE, INFINITE);
if (rc < WAIT_OBJECT_0 || rc >= WAIT_OBJECT_0 + eventCount) {
break;
}
unsigned int eventIndex = rc - WAIT_OBJECT_0;
HANDLE hEvent = eventHandles[eventIndex];
// Has the device been stopped? If so, need to get out of this loop.
if (hEvent == pDevice->dsound.hStopEvent) {
break;
}
// If we get here it means the event that's been signaled represents a fragment.
unsigned int fragmentIndex = eventIndex; // <-- Just for clarity.
mal_assert(fragmentIndex < pDevice->fragmentCount);
if (pDevice->type == mal_device_type_playback) {
mal_device__read_fragment_from_client__dsound(pDevice, (fragmentIndex + 1) % pDevice->fragmentCount);
} else {
mal_device__send_fragment_to_client__dsound(pDevice, fragmentIndex);
}
}
}
return (mal_thread_result)0;
}
typedef struct typedef struct
{ {
...@@ -916,17 +1009,6 @@ void mal_device_uninit__dsound(mal_device* pDevice) ...@@ -916,17 +1009,6 @@ void mal_device_uninit__dsound(mal_device* pDevice)
{ {
mal_assert(pDevice != NULL); mal_assert(pDevice != NULL);
pDevice->flags |= MAL_FLAG_TERMINATING;
if (pDevice->flags & MAL_FLAG_DSOUND_HAS_THREAD) {
mal_semaphore_release(&pDevice->dsound.semaphore);
mal_thread_wait(&pDevice->dsound.thread);
}
if (pDevice->flags & MAL_FLAG_DSOUND_HAS_SEMAPHORE) {
mal_semaphore_delete(&pDevice->dsound.semaphore);
}
if (pDevice->dsound.hDSoundDLL != NULL) { if (pDevice->dsound.hDSoundDLL != NULL) {
if (pDevice->dsound.hStopEvent) { if (pDevice->dsound.hStopEvent) {
CloseHandle(pDevice->dsound.hStopEvent); CloseHandle(pDevice->dsound.hStopEvent);
...@@ -1186,100 +1268,242 @@ mal_result mal_device_init__dsound(mal_device* pDevice, mal_device_type type, ma ...@@ -1186,100 +1268,242 @@ mal_result mal_device_init__dsound(mal_device* pDevice, mal_device_type type, ma
} }
if (!mal_semaphore_create(&pDevice->dsound.semaphore, 0)) {
mal_device_uninit__dsound(pDevice);
return MAL_FAILED_TO_INIT_BACKEND;
}
pDevice->flags |= MAL_FLAG_DSOUND_HAS_SEMAPHORE;
if (!mal_thread_create(&pDevice->dsound.thread, mal_worker_thread__dsound, pDevice)) {
mal_device_uninit__dsound(pDevice);
return MAL_FAILED_TO_INIT_BACKEND;
}
pDevice->flags |= MAL_FLAG_DSOUND_HAS_THREAD;
return MAL_SUCCESS; return MAL_SUCCESS;
} }
mal_result mal_device_start__dsound(mal_device* pDevice)
static mal_result mal_device__read_fragment_from_client__dsound(mal_device* pDevice, mal_uint32 fragmentIndex)
{ {
mal_assert(pDevice != NULL); mal_assert(pDevice != NULL);
// We don't actually start the device here. We instead signal the semaphore on the worker thread and DWORD fragmentSizeInBytes = pDevice->fragmentSizeInFrames * pDevice->channels * mal_get_sample_size_in_bytes(pDevice->format);
// let that thread play the device. DWORD offset = fragmentIndex * fragmentSizeInBytes;
pDevice->flags |= MAL_FLAG_STARTED;
mal_semaphore_release(&pDevice->dsound.semaphore);
// Make sure the signal used to stop the worker thread is no longer signaled. void* pLockPtr;
ResetEvent(pDevice->dsound.hStopEvent); DWORD lockSize;
return MAL_SUCCESS; if (FAILED(IDirectSoundBuffer_Lock((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackBuffer, offset, fragmentSizeInBytes, &pLockPtr, &lockSize, NULL, NULL, 0))) {
return MAL_ERROR;
}
mal_device__read_samples_from_client(pDevice, pDevice->fragmentSizeInFrames * pDevice->channels, pLockPtr);
IDirectSoundBuffer_Unlock((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackBuffer, pLockPtr, lockSize, NULL, 0);
return MAL_SUCCESS;
} }
mal_result mal_device_stop__dsound(mal_device* pDevice) static mal_result mal_device__send_fragment_to_client__dsound(mal_device* pDevice, mal_uint32 fragmentIndex)
{ {
mal_assert(pDevice != NULL); mal_assert(pDevice != NULL);
// The device is not stopped here. We instead mark the device as stopped and signal the semaphore
// on the worker thread.
pDevice->flags &= ~MAL_FLAG_STARTED;
mal_semaphore_release(&pDevice->dsound.semaphore);
// The worker thread is likely waiting on DWORD fragmentSizeInBytes = pDevice->fragmentSizeInFrames * pDevice->channels * mal_get_sample_size_in_bytes(pDevice->format);
SetEvent(pDevice->dsound.hStopEvent); DWORD offset = fragmentIndex * fragmentSizeInBytes;
return MAL_SUCCESS;
}
#endif
void* pLockPtr;
DWORD lockSize;
if (FAILED(IDirectSoundCaptureBuffer_Lock((LPDIRECTSOUNDCAPTUREBUFFER8)pDevice->dsound.pCaptureBuffer, offset, fragmentSizeInBytes, &pLockPtr, &lockSize, NULL, NULL, 0))) {
return MAL_ERROR;
}
/////////////////////////////////////////////////////////////////////////////// mal_device__send_samples_to_client(pDevice, pDevice->fragmentSizeInFrames * pDevice->channels, pLockPtr);
//
// ALSA Backend
//
///////////////////////////////////////////////////////////////////////////////
#ifdef MAL_ENABLE_ALSA
#include <alsa/asoundlib.h>
#define MAL_FLAG_ALSA_USING_MMAP (1 << 15) IDirectSoundCaptureBuffer_Unlock((LPDIRECTSOUNDCAPTUREBUFFER)pDevice->dsound.pCaptureBuffer, pLockPtr, lockSize, NULL, 0);
#define MAL_FLAG_ALSA_HAS_SEMAPHORE (1 << 16) // Used for cleanup. return MAL_SUCCESS;
#define MAL_FLAG_ALSA_HAS_THREAD (1 << 17) // Used for cleanup. }
mal_bool32 mal_device_write__alsa(mal_device* pDevice)
static mal_result mal_device__start_backend__dsound(mal_device* pDevice)
{ {
mal_assert(pDevice != NULL); mal_assert(pDevice != NULL);
if (!mal_device_is_started(pDevice)) {
return MAL_FALSE; if (pDevice->type == mal_device_type_playback) {
} // Before playing anything we need to grab an initial fragment of sample data from the client.
if (mal_device__read_fragment_from_client__dsound(pDevice, 0) != MAL_SUCCESS) {
void* pBuffer = NULL; return MAL_FAILED_TO_READ_DATA_FROM_CLIENT;
if (pDevice->alsa.pIntermediaryBuffer == NULL) { }
if (IDirectSoundBuffer_Play((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackBuffer, 0, 0, DSBPLAY_LOOPING) != DS_OK) {
return MAL_FAILED_TO_START_BACKEND_DEVICE;
}
} else {
if (IDirectSoundCaptureBuffer8_Start((LPDIRECTSOUNDCAPTUREBUFFER8)pDevice->dsound.pCaptureBuffer, DSCBSTART_LOOPING) != DS_OK) {
return MAL_FAILED_TO_START_BACKEND_DEVICE;
}
}
return MAL_SUCCESS;
}
static mal_result mal_device__stop_backend__dsound(mal_device* pDevice)
{
mal_assert(pDevice != NULL);
if (pDevice->type == mal_device_type_playback) {
if (IDirectSoundBuffer_Stop((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackBuffer) != DS_OK) {
return MAL_FAILED_TO_STOP_BACKEND_DEVICE;
}
IDirectSoundBuffer_SetCurrentPosition((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackBuffer, 0);
} else {
if (IDirectSoundCaptureBuffer_Stop((LPDIRECTSOUNDCAPTUREBUFFER)pDevice->dsound.pCaptureBuffer) != DS_OK) {
return MAL_FAILED_TO_STOP_BACKEND_DEVICE;
}
}
return MAL_SUCCESS;
}
static mal_result mal_device__break_main_loop__dsound(mal_device* pDevice)
{
mal_assert(pDevice != NULL);
// The main loop will be waiting on a bunch of events via the WaitForMultipleObjects() API. One of those events
// is a special event we use for forcing that function to return.
SetEvent(pDevice->dsound.hStopEvent);
return MAL_SUCCESS;
}
static mal_result mal_device__main_loop__dsound(mal_device* pDevice)
{
mal_assert(pDevice != NULL);
// Make sure the stop event is not signaled to ensure we don't end up immediately returning from WaitForMultipleObjects().
ResetEvent(pDevice->dsound.hStopEvent);
// When the device is first started, there will be a few fragments that we need to skip over due to the way
// they're handled by DirectSound. For a recording device it's the first fragment we need to ignore.
if (pDevice->type == mal_device_type_playback) {
pDevice->dsound.ignoredFragmentCounter = 0;
} else {
pDevice->dsound.ignoredFragmentCounter = 1;
}
for (;;) {
// Wait for a notification. Notifications are tied to fragments.
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);
eventHandles[eventCount-1] = pDevice->dsound.hStopEvent;
DWORD rc = WaitForMultipleObjects(eventCount, eventHandles, FALSE, INFINITE);
if (rc < WAIT_OBJECT_0 || rc >= WAIT_OBJECT_0 + eventCount) {
break;
}
unsigned int eventIndex = rc - WAIT_OBJECT_0;
HANDLE hEvent = eventHandles[eventIndex];
// Has the device been stopped? If so, need to get out of this loop.
if (hEvent == pDevice->dsound.hStopEvent) {
break;
}
// Some initial fragments need to be skipped over.
if (pDevice->dsound.ignoredFragmentCounter > 0) {
pDevice->dsound.ignoredFragmentCounter -= 1;
continue;
}
// If we get here it means the event that's been signaled represents a fragment.
unsigned int fragmentIndex = eventIndex; // <-- Just for clarity.
mal_assert(fragmentIndex < pDevice->fragmentCount);
if (pDevice->type == mal_device_type_playback) {
mal_device__read_fragment_from_client__dsound(pDevice, (fragmentIndex + 1) % pDevice->fragmentCount);
} else {
mal_device__send_fragment_to_client__dsound(pDevice, fragmentIndex);
}
}
return MAL_SUCCESS;
}
#endif
///////////////////////////////////////////////////////////////////////////////
//
// ALSA Backend
//
///////////////////////////////////////////////////////////////////////////////
#ifdef MAL_ENABLE_ALSA
#include <alsa/asoundlib.h>
// 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.
//
// This will return early if the main loop is broken with mal_device__break_main_loop().
mal_uint32 mal_device__wait_for_frames(mal_device* pDevice)
{
mal_assert(pDevice != NULL);
while (!pDevice->alsa.breakFromMainLoop) {
snd_pcm_sframes_t framesAvailable = snd_pcm_avail_update(pDevice->alsa.pPCM);
if (framesAvailable >= pDevice->fragmentSizeInFrames) {
return pDevice->fragmentSizeInFrames;
}
if (framesAvailable < 0) {
if (framesAvailable == -EPIPE) {
if (snd_pcm_recover(pDevice->alsa.pPCM, framesAvailable, MAL_TRUE) < 0) {
return MAL_FALSE;
}
framesAvailable = snd_pcm_avail_update(pDevice->alsa.pPCM);
if (framesAvailable < 0) {
return MAL_FALSE;
}
}
}
const int timeoutInMilliseconds = 20; // <-- The larger this value, the longer it'll take to stop the device!
int waitResult = snd_pcm_wait(pDevice->alsa.pPCM, timeoutInMilliseconds);
if (waitResult < 0) {
snd_pcm_recover(pDevice->alsa.pPCM, waitResult, MAL_TRUE);
}
}
// We'll get here if the loop was terminated. Just return whatever's available.
snd_pcm_sframes_t framesAvailable = snd_pcm_avail(pDevice->alsa.pPCM);
if (framesAvailable < 0) {
return 0;
}
return framesAvailable;
}
mal_bool32 mal_device_write__alsa(mal_device* pDevice)
{
mal_assert(pDevice != NULL);
if (!mal_device_is_started(pDevice)) {
return MAL_FALSE;
}
if (pDevice->alsa.breakFromMainLoop) {
return MAL_FALSE;
}
void* pBuffer = NULL;
if (pDevice->alsa.pIntermediaryBuffer == NULL) {
// mmap. // mmap.
return MAL_FALSE; return MAL_FALSE;
} else { } else {
// readi/writei. // readi/writei.
pBuffer = pDevice->alsa.pIntermediaryBuffer; pBuffer = pDevice->alsa.pIntermediaryBuffer;
} }
mal_uint32 desiredSampleCount = pDevice->fragmentSizeInFrames * pDevice->channels;
mal_uint32 samplesRead = 0;
if (pDevice->onSend) {
samplesRead = pDevice->onSend(pDevice, desiredSampleCount, pBuffer);
if (samplesRead != desiredSampleCount) {
// Not enough samples were read. Fill the remainder with silence.
mal_uint32 sampleSize = mal_get_sample_size_in_bytes(pDevice->format);
mal_uint32 consumedBytes = samplesRead*sampleSize;
mal_uint32 remainingBytes = (desiredSampleCount-samplesRead)*sampleSize;
mal_zero_memory((mal_uint8*)pBuffer + consumedBytes, remainingBytes);
}
}
if (pDevice->alsa.pIntermediaryBuffer == NULL) { if (pDevice->alsa.pIntermediaryBuffer == NULL) {
// mmap. // mmap.
} else { } else {
// readi/writei. // readi/writei.
for (;;) { while (!pDevice->alsa.breakFromMainLoop) {
snd_pcm_sframes_t framesWritten = snd_pcm_writei(pDevice->alsa.pPCM, pDevice->alsa.pIntermediaryBuffer, pDevice->fragmentSizeInFrames); mal_uint32 framesAvailable = mal_device__wait_for_frames(pDevice);
if (framesAvailable == 0) {
return MAL_FALSE;
}
mal_device__read_samples_from_client(pDevice, framesAvailable * pDevice->channels, pBuffer);
snd_pcm_sframes_t framesWritten = snd_pcm_writei(pDevice->alsa.pPCM, pDevice->alsa.pIntermediaryBuffer, framesAvailable);
if (framesWritten < 0) { if (framesWritten < 0) {
if (framesWritten == -EAGAIN) { if (framesWritten == -EAGAIN) {
continue; // Just keep trying... continue; // Just keep trying...
...@@ -1289,7 +1513,7 @@ mal_bool32 mal_device_write__alsa(mal_device* pDevice) ...@@ -1289,7 +1513,7 @@ mal_bool32 mal_device_write__alsa(mal_device* pDevice)
return MAL_FALSE; return MAL_FALSE;
} }
framesWritten = snd_pcm_writei(pDevice->alsa.pPCM, pDevice->alsa.pIntermediaryBuffer, pDevice->fragmentSizeInFrames); framesWritten = snd_pcm_writei(pDevice->alsa.pPCM, pDevice->alsa.pIntermediaryBuffer, framesAvailable);
if (framesWritten < 0) { if (framesWritten < 0) {
return MAL_FALSE; return MAL_FALSE;
} }
...@@ -1313,6 +1537,9 @@ mal_bool32 mal_device_read__alsa(mal_device* pDevice) ...@@ -1313,6 +1537,9 @@ mal_bool32 mal_device_read__alsa(mal_device* pDevice)
if (!mal_device_is_started(pDevice)) { if (!mal_device_is_started(pDevice)) {
return MAL_FALSE; return MAL_FALSE;
} }
if (pDevice->alsa.breakFromMainLoop) {
return MAL_FALSE;
}
mal_uint32 samplesToSend = 0; mal_uint32 samplesToSend = 0;
void* pBuffer = NULL; void* pBuffer = NULL;
...@@ -1322,8 +1549,13 @@ mal_bool32 mal_device_read__alsa(mal_device* pDevice) ...@@ -1322,8 +1549,13 @@ mal_bool32 mal_device_read__alsa(mal_device* pDevice)
} else { } else {
// readi/writei. // readi/writei.
snd_pcm_sframes_t framesRead = 0; snd_pcm_sframes_t framesRead = 0;
for (;;) { while (!pDevice->alsa.breakFromMainLoop) {
framesRead = snd_pcm_readi(pDevice->alsa.pPCM, pDevice->alsa.pIntermediaryBuffer, pDevice->fragmentSizeInFrames); mal_uint32 framesAvailable = mal_device__wait_for_frames(pDevice);
if (framesAvailable == 0) {
return MAL_FALSE;
}
framesRead = snd_pcm_readi(pDevice->alsa.pPCM, pDevice->alsa.pIntermediaryBuffer, framesAvailable);
if (framesRead < 0) { if (framesRead < 0) {
if (framesRead == -EAGAIN) { if (framesRead == -EAGAIN) {
continue; // Just keep trying... continue; // Just keep trying...
...@@ -1333,7 +1565,7 @@ mal_bool32 mal_device_read__alsa(mal_device* pDevice) ...@@ -1333,7 +1565,7 @@ mal_bool32 mal_device_read__alsa(mal_device* pDevice)
return MAL_FALSE; return MAL_FALSE;
} }
snd_pcm_sframes_t framesRead = snd_pcm_readi(pDevice->alsa.pPCM, pDevice->alsa.pIntermediaryBuffer, pDevice->fragmentSizeInFrames); framesRead = snd_pcm_readi(pDevice->alsa.pPCM, pDevice->alsa.pIntermediaryBuffer, pDevice->fragmentSizeInFrames);
if (framesRead < 0) { if (framesRead < 0) {
return MAL_FALSE; return MAL_FALSE;
} }
...@@ -1367,43 +1599,6 @@ mal_bool32 mal_device_read__alsa(mal_device* pDevice) ...@@ -1367,43 +1599,6 @@ mal_bool32 mal_device_read__alsa(mal_device* pDevice)
} }
mal_thread_result MAL_THREADCALL mal_worker_thread__alsa(void* pData)
{
mal_device* pDevice = (mal_device*)pData;
mal_assert(pDevice != NULL);
for (;;) {
mal_semaphore_wait(&pDevice->alsa.semaphore);
// Just break if we're terminating.
if (pDevice->flags & MAL_FLAG_TERMINATING) {
break;
}
// Continue if the device has been stopped.
if (!mal_device_is_started(pDevice)) {
snd_pcm_drop(pDevice->alsa.pPCM);
continue;
}
// Getting here means we just started the device and we need to wait for the device to
// either deliver us data (recording) or request more data (playback).
snd_pcm_prepare(pDevice->alsa.pPCM);
if (pDevice->type == mal_device_type_playback) {
// Playback. Read from client, write to device.
while (mal_device_write__alsa(pDevice)) {
}
} else {
// Playback. Read from device, write to client.
while (mal_device_read__alsa(pDevice)) {
}
}
}
return (mal_thread_result)0;
}
mal_result mal_enumerate_devices__alsa(mal_device_type type, mal_uint32* pCount, mal_device_info* pInfo) mal_result mal_enumerate_devices__alsa(mal_device_type type, mal_uint32* pCount, mal_device_info* pInfo)
{ {
mal_uint32 infoSize = *pCount; mal_uint32 infoSize = *pCount;
...@@ -1411,7 +1606,7 @@ mal_result mal_enumerate_devices__alsa(mal_device_type type, mal_uint32* pCount, ...@@ -1411,7 +1606,7 @@ mal_result mal_enumerate_devices__alsa(mal_device_type type, mal_uint32* pCount,
char** ppDeviceHints; char** ppDeviceHints;
if (snd_device_name_hint(-1, "pcm", (void***)&ppDeviceHints) < 0) { if (snd_device_name_hint(-1, "pcm", (void***)&ppDeviceHints) < 0) {
return MAL_UNKNOWN_ERROR; return MAL_ERROR;
} }
char** ppNextDeviceHint = ppDeviceHints; char** ppNextDeviceHint = ppDeviceHints;
...@@ -1452,17 +1647,6 @@ void mal_device_uninit__alsa(mal_device* pDevice) ...@@ -1452,17 +1647,6 @@ void mal_device_uninit__alsa(mal_device* pDevice)
{ {
mal_assert(pDevice != NULL); mal_assert(pDevice != NULL);
pDevice->flags |= MAL_FLAG_TERMINATING;
if (pDevice->flags & MAL_FLAG_ALSA_HAS_THREAD) {
mal_semaphore_release(&pDevice->alsa.semaphore);
mal_thread_wait(&pDevice->alsa.thread);
}
if (pDevice->flags & MAL_FLAG_ALSA_HAS_SEMAPHORE) {
mal_semaphore_delete(&pDevice->alsa.semaphore);
}
if (pDevice->alsa.pPCM) { if (pDevice->alsa.pPCM) {
snd_pcm_close((snd_pcm_t*)pDevice->alsa.pPCM); snd_pcm_close((snd_pcm_t*)pDevice->alsa.pPCM);
...@@ -1608,7 +1792,7 @@ mal_result mal_device_init__alsa(mal_device* pDevice, mal_device_type type, mal_ ...@@ -1608,7 +1792,7 @@ mal_result mal_device_init__alsa(mal_device* pDevice, mal_device_type type, mal_
// 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->flags & MAL_FLAG_ALSA_USING_MMAP)) { if (!pDevice->alsa.isUsingMMap) {
pDevice->alsa.pIntermediaryBuffer = mal_malloc(pDevice->fragmentSizeInFrames * pDevice->channels * mal_get_sample_size_in_bytes(pDevice->format)); pDevice->alsa.pIntermediaryBuffer = mal_malloc(pDevice->fragmentSizeInFrames * 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);
...@@ -1618,59 +1802,223 @@ mal_result mal_device_init__alsa(mal_device* pDevice, mal_device_type type, mal_ ...@@ -1618,59 +1802,223 @@ mal_result mal_device_init__alsa(mal_device* pDevice, mal_device_type type, mal_
if (!mal_semaphore_create(&pDevice->alsa.semaphore, 0)) { return MAL_SUCCESS;
mal_device_uninit__alsa(pDevice); }
return MAL_FAILED_TO_INIT_BACKEND;
static mal_result mal_device__start_backend__alsa(mal_device* pDevice)
{
mal_assert(pDevice != NULL);
// Prepare the device first...
snd_pcm_prepare(pDevice->alsa.pPCM);
// ... and then grab an initial fragment 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);
} else {
snd_pcm_start(pDevice->alsa.pPCM);
} }
pDevice->flags |= MAL_FLAG_ALSA_HAS_SEMAPHORE;
return MAL_SUCCESS;
}
static mal_result mal_device__stop_backend__alsa(mal_device* pDevice)
{
mal_assert(pDevice != NULL);
snd_pcm_drop(pDevice->alsa.pPCM);
return MAL_SUCCESS;
}
static mal_result mal_device__break_main_loop__alsa(mal_device* pDevice)
{
mal_assert(pDevice != NULL);
// The main loop will be waiting on snd_pcm_writei()/snd_pcm_readi(). The only way I was able to
// figure out how to force these to return is to prepare the device. Not sure if this is the best
// way to do this...
//
// Update #1: This causes snd_pcm_readi() to return -EIO on it's first fragment, so no good.
//snd_pcm_prepare(pDevice->alsa.pPCM);
if (!mal_thread_create(&pDevice->alsa.thread, mal_worker_thread__alsa, pDevice)) { // Fallback. We just set a variable to tell the worker thread to terminate after handling the
mal_device_uninit__alsa(pDevice); // next fragment. This is a slow way of handling this.
return MAL_FAILED_TO_INIT_BACKEND; pDevice->alsa.breakFromMainLoop = MAL_TRUE;
return MAL_SUCCESS;
}
static mal_result mal_device__main_loop__alsa(mal_device* pDevice)
{
mal_assert(pDevice != NULL);
pDevice->alsa.breakFromMainLoop = MAL_FALSE;
if (pDevice->type == mal_device_type_playback) {
// Playback. Read from client, write to device.
while (!pDevice->alsa.breakFromMainLoop && mal_device_write__alsa(pDevice)) {
}
} else {
// Playback. Read from device, write to client.
while (!pDevice->alsa.breakFromMainLoop && mal_device_read__alsa(pDevice)) {
}
} }
pDevice->flags |= MAL_FLAG_ALSA_HAS_THREAD;
return MAL_SUCCESS;
return MAL_SUCCESS;
} }
#endif
mal_result mal_device_start__alsa(mal_device* pDevice) static mal_result mal_device__start_backend(mal_device* pDevice)
{ {
mal_assert(pDevice != NULL); mal_assert(pDevice != NULL);
// We don't actually start the device here. We instead signal the semaphore on the worker thread and mal_result result = MAL_NO_BACKEND;
// let that thread do the device preparation. #ifdef MAL_ENABLE_DSOUND
pDevice->flags |= MAL_FLAG_STARTED; if (pDevice->api == mal_api_dsound) {
mal_semaphore_release(&pDevice->alsa.semaphore); result = mal_device__start_backend__dsound(pDevice);
return MAL_SUCCESS; }
#endif
#ifdef MAL_ENABLE_ALSA
if (pDevice->api == mal_api_alsa) {
result = mal_device__start_backend__alsa(pDevice);
}
#endif
#ifdef MAL_ENABLE_NULL
if (pDevice->api == mal_api_null) {
result = mal_device__start_backend__null(pDevice);
}
#endif
return result;
} }
mal_result mal_device_stop__alsa(mal_device* pDevice) static mal_result mal_device__stop_backend(mal_device* pDevice)
{ {
mal_assert(pDevice != NULL); mal_assert(pDevice != NULL);
// The device is not stopped here. We instead mark the device as stopped and signal the semaphore mal_result result = MAL_NO_BACKEND;
// on the worker thread. #ifdef MAL_ENABLE_DSOUND
pDevice->flags &= ~MAL_FLAG_STARTED; if (pDevice->api == mal_api_dsound) {
mal_semaphore_release(&pDevice->alsa.semaphore); result = mal_device__stop_backend__dsound(pDevice);
}
// If we are in snd_pcm_writei()/snd_pcm_readi() we won't return from it until it's signaled. It #endif
// appears from my admittedly limited research and experimentation that we can signal the PCM with #ifdef MAL_ENABLE_ALSA
// snd_pcm_prepare(). If don't do this the thread will still return, but it'll wait for the next if (pDevice->api == mal_api_alsa) {
// fragment to be processed which might take some time depending on the size of the fragment. result = mal_device__stop_backend__alsa(pDevice);
// }
// I'm not sure if this is the proper way to do this so best look into this. #endif
snd_pcm_prepare(pDevice->alsa.pPCM); #ifdef MAL_ENABLE_NULL
return MAL_SUCCESS; if (pDevice->api == mal_api_null) {
result = mal_device__stop_backend__null(pDevice);
}
#endif
return result;
} }
static mal_result mal_device__break_main_loop(mal_device* pDevice)
{
mal_assert(pDevice != NULL);
mal_result result = MAL_NO_BACKEND;
#ifdef MAL_ENABLE_DSOUND
if (pDevice->api == mal_api_dsound) {
result = mal_device__break_main_loop__dsound(pDevice);
}
#endif
#ifdef MAL_ENABLE_ALSA
if (pDevice->api == mal_api_alsa) {
result = mal_device__break_main_loop__alsa(pDevice);
}
#endif #endif
#ifdef MAL_ENABLE_NULL
if (pDevice->api == mal_api_null) {
result = mal_device__break_main_loop__null(pDevice);
}
#endif
return result;
}
static mal_result mal_device__main_loop(mal_device* pDevice)
{
mal_assert(pDevice != NULL);
mal_result result = MAL_NO_BACKEND;
#ifdef MAL_ENABLE_DSOUND
if (pDevice->api == mal_api_dsound) {
result = mal_device__main_loop__dsound(pDevice);
}
#endif
#ifdef MAL_ENABLE_ALSA
if (pDevice->api == mal_api_alsa) {
result = mal_device__main_loop__alsa(pDevice);
}
#endif
#ifdef MAL_ENABLE_NULL
if (pDevice->api == mal_api_null) {
result = mal_device__main_loop__null(pDevice);
}
#endif
return result;
}
mal_thread_result MAL_THREADCALL mal_worker_thread(void* pData)
{
mal_device* pDevice = (mal_device*)pData;
mal_assert(pDevice != NULL);
for (;;) {
// At the start of iteration the device is stopped - we must explicitly mark it as such.
mal_device__stop_backend(pDevice);
// Let the other threads know that the device has stopped.
mal_device__set_state(pDevice, MAL_STATE_STOPPED);
mal_event_signal(&pDevice->stopEvent);
// We use an event to wait for a request to wake up.
mal_event_wait(&pDevice->wakeupEvent);
// Default result code.
pDevice->workResult = MAL_SUCCESS;
// Just break if we're terminating.
if (mal_device__get_state(pDevice) == MAL_STATE_UNINITIALIZED) {
break;
}
// Getting here means we just started the device and we need to wait for the device to
// either deliver us data (recording) or request more data (playback).
mal_assert(mal_device__get_state(pDevice) == MAL_STATE_STARTING);
pDevice->workResult = mal_device__start_backend(pDevice);
if (pDevice->workResult != MAL_SUCCESS) {
mal_event_signal(&pDevice->startEvent);
continue;
}
// The thread that requested the device to start playing is waiting for this thread to start the
// device for real, which is now.
mal_device__set_state(pDevice, MAL_STATE_STARTED);
mal_event_signal(&pDevice->startEvent);
// Now we just enter the main loop. The main loop can be broken with mal_device__break_main_loop().
mal_device__main_loop(pDevice);
}
// Make sure we aren't continuously waiting on a stop event.
mal_event_signal(&pDevice->stopEvent); // <-- Is this still needed?
return (mal_thread_result)0;
}
// Helper for determining whether or not the given device is initialized. // Helper for determining whether or not the given device is initialized.
mal_bool32 mal_device__is_initialized(mal_device* pDevice) mal_bool32 mal_device__is_initialized(mal_device* pDevice)
{ {
if (pDevice == NULL) return MAL_FALSE; if (pDevice == NULL) return MAL_FALSE;
return (pDevice->flags & MAL_FLAG_INITIALIZED) != 0; return mal_device__get_state(pDevice) != MAL_STATE_UNINITIALIZED;
} }
...@@ -1704,6 +2052,10 @@ mal_result mal_device_init(mal_device* pDevice, mal_device_type type, mal_device ...@@ -1704,6 +2052,10 @@ mal_result mal_device_init(mal_device* pDevice, mal_device_type type, mal_device
{ {
if (pDevice == NULL) return MAL_INVALID_ARGS; if (pDevice == NULL) return MAL_INVALID_ARGS;
mal_zero_object(pDevice); mal_zero_object(pDevice);
if (((mal_uint64)pDevice % sizeof(pDevice)) != 0) {
// TODO: Emit a warning that the device is not thread safe.
}
if (channels == 0 || sampleRate == 0 || fragmentSizeInFrames == 0 || fragmentCount == 0) return MAL_INVALID_ARGS; if (channels == 0 || sampleRate == 0 || fragmentSizeInFrames == 0 || fragmentCount == 0) return MAL_INVALID_ARGS;
...@@ -1714,6 +2066,26 @@ mal_result mal_device_init(mal_device* pDevice, mal_device_type type, mal_device ...@@ -1714,6 +2066,26 @@ mal_result mal_device_init(mal_device* pDevice, mal_device_type type, mal_device
pDevice->fragmentSizeInFrames = fragmentSizeInFrames; pDevice->fragmentSizeInFrames = fragmentSizeInFrames;
pDevice->fragmentCount = fragmentCount; pDevice->fragmentCount = fragmentCount;
// When the device is started, the worker thread is the one that does the actual startup of the backend device. We
// use a semaphore to wait for the background thread to finish the work. The same applies for stopping the device.
//
// Each of these semaphores is released internally by the worker thread when the work is completed. The start
// semaphore is also used to wake up the worker thread.
if (!mal_event_create(&pDevice->wakeupEvent)) {
return MAL_FAILED_TO_INIT_BACKEND;
}
if (!mal_event_create(&pDevice->startEvent)) {
mal_event_delete(&pDevice->wakeupEvent);
return MAL_FAILED_TO_INIT_BACKEND;
}
if (!mal_event_create(&pDevice->stopEvent)) {
mal_event_delete(&pDevice->startEvent);
mal_event_delete(&pDevice->wakeupEvent);
return MAL_FAILED_TO_INIT_BACKEND;
}
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) {
...@@ -1737,7 +2109,18 @@ mal_result mal_device_init(mal_device* pDevice, mal_device_type type, mal_device ...@@ -1737,7 +2109,18 @@ mal_result mal_device_init(mal_device* pDevice, mal_device_type type, mal_device
return MAL_NO_BACKEND; return MAL_NO_BACKEND;
} }
pDevice->flags |= MAL_FLAG_INITIALIZED;
// The worker thread.
if (!mal_thread_create(&pDevice->thread, mal_worker_thread, pDevice)) {
mal_device_uninit(pDevice);
return MAL_FAILED_TO_INIT_BACKEND;
}
// Wait for the worker thread to put the device into it's stopped state for real.
mal_event_wait(&pDevice->stopEvent);
mal_assert(mal_device__get_state(pDevice) == MAL_STATE_STOPPED);
return MAL_SUCCESS; return MAL_SUCCESS;
} }
...@@ -1749,6 +2132,17 @@ void mal_device_uninit(mal_device* pDevice) ...@@ -1749,6 +2132,17 @@ void mal_device_uninit(mal_device* pDevice)
// but I like to do it explicitly for my own sanity. // but I like to do it explicitly for my own sanity.
mal_device_stop(pDevice); mal_device_stop(pDevice);
// Putting the device into an uninitialized state will make the worker thread return.
mal_device__set_state(pDevice, MAL_STATE_UNINITIALIZED);
// Wake up the worker thread and wait for it to properly terminate.
mal_event_signal(&pDevice->wakeupEvent);
mal_thread_wait(&pDevice->thread);
mal_event_delete(&pDevice->stopEvent);
mal_event_delete(&pDevice->startEvent);
mal_event_delete(&pDevice->wakeupEvent);
#ifdef MAL_ENABLE_DSOUND #ifdef MAL_ENABLE_DSOUND
if (pDevice->api == mal_api_dsound) { if (pDevice->api == mal_api_dsound) {
mal_device_uninit__dsound(pDevice); mal_device_uninit__dsound(pDevice);
...@@ -1773,85 +2167,80 @@ void mal_device_uninit(mal_device* pDevice) ...@@ -1773,85 +2167,80 @@ void mal_device_uninit(mal_device* pDevice)
void mal_device_set_recv_callback(mal_device* pDevice, mal_recv_proc proc) void mal_device_set_recv_callback(mal_device* pDevice, mal_recv_proc proc)
{ {
if (pDevice == NULL) return; if (pDevice == NULL) return;
pDevice->onRecv = proc; mal_atomic_exchange_ptr(&pDevice->onRecv, proc);
} }
void mal_device_set_send_callback(mal_device* pDevice, mal_send_proc proc) void mal_device_set_send_callback(mal_device* pDevice, mal_send_proc proc)
{ {
if (pDevice == NULL) return; if (pDevice == NULL) return;
pDevice->onSend = proc; mal_atomic_exchange_ptr(&pDevice->onSend, proc);
} }
mal_result mal_device_start(mal_device* pDevice) mal_result mal_device_start(mal_device* pDevice)
{ {
if (pDevice == NULL) return MAL_INVALID_ARGS; if (pDevice == NULL) return MAL_INVALID_ARGS;
if (mal_device_is_started(pDevice)) { if (mal_device__get_state(pDevice) == MAL_STATE_UNINITIALIZED) return MAL_DEVICE_NOT_INITIALIZED;
return MAL_DEVICE_ALREADY_STARTED;
}
mal_result result = MAL_NO_BACKEND;
#ifdef MAL_ENABLE_DSOUND
if (pDevice->api == mal_api_dsound) {
result = mal_device_start__dsound(pDevice);
}
#endif
#ifdef MAL_ENABLE_ALSA // Be a bit more descriptive if the device is already started or is already in the process of starting. This is likely
if (pDevice->api == mal_api_alsa) { // a bug with the application.
result = mal_device_start__alsa(pDevice); if (mal_device__get_state(pDevice) == MAL_STATE_STARTING) {
} return MAL_DEVICE_ALREADY_STARTING;
#endif }
if (mal_device__get_state(pDevice) == MAL_STATE_STARTED) {
return MAL_DEVICE_ALREADY_STARTED;
}
#ifdef MAL_ENABLE_NULL // The device needs to be in a stopped state. If it's not, we just let the caller know the device is busy.
if (pDevice->api == mal_api_null) { if (mal_device__get_state(pDevice) != MAL_STATE_STOPPED) {
result = mal_device_start__null(pDevice); return MAL_DEVICE_BUSY;
} }
#endif
if (result == MAL_SUCCESS) { mal_device__set_state(pDevice, MAL_STATE_STARTING);
pDevice->flags |= MAL_FLAG_STARTED; mal_event_signal(&pDevice->wakeupEvent);
}
return result; // Wait for the worker thread to finish starting the device. Note that the worker thread will be the one
// who puts the device into the started state. Don't call mal_device__set_state() here.
mal_event_wait(&pDevice->startEvent);
return pDevice->workResult;
} }
mal_result mal_device_stop(mal_device* pDevice) mal_result mal_device_stop(mal_device* pDevice)
{ {
if (pDevice == NULL) return MAL_INVALID_ARGS; if (pDevice == NULL) return MAL_INVALID_ARGS;
if (!mal_device_is_started(pDevice)) { if (mal_device__get_state(pDevice) == MAL_STATE_UNINITIALIZED) return MAL_DEVICE_NOT_INITIALIZED;
return MAL_DEVICE_ALREADY_STOPPED;
}
mal_result result = MAL_NO_BACKEND;
#ifdef MAL_ENABLE_DSOUND
if (pDevice->api == mal_api_dsound) {
result = mal_device_stop__dsound(pDevice);
}
#endif
#ifdef MAL_ENABLE_ALSA // Be a bit more descriptive if the device is already stopped or is already in the process of stopping. This is likely
if (pDevice->api == mal_api_alsa) { // a bug with the application.
result = mal_device_stop__alsa(pDevice); if (mal_device__get_state(pDevice) == MAL_STATE_STOPPING) {
} return MAL_DEVICE_ALREADY_STOPPING;
#endif }
if (mal_device__get_state(pDevice) == MAL_STATE_STOPPED) {
return MAL_DEVICE_ALREADY_STOPPED;
}
#ifdef MAL_ENABLE_NULL // The device needs to be in a started state. If it's not, we just let the caller know the device is busy.
if (pDevice->api == mal_api_null) { if (mal_device__get_state(pDevice) != MAL_STATE_STARTED) {
result = mal_device_stop__null(pDevice); return MAL_DEVICE_BUSY;
} }
#endif
if (result == MAL_SUCCESS) { mal_device__set_state(pDevice, MAL_STATE_STOPPING);
pDevice->flags &= ~MAL_FLAG_STARTED;
} // There's no need to wake up the thread like we do when starting.
return result; // When we get here the worker thread is likely in a wait state while waiting for the backend device to deliver or request
// audio data. We need to force these to return as quickly as possible.
mal_device__break_main_loop(pDevice);
// We need to wait for the worker thread to become available for work before returning. Note that the worker thread will be
// the one who puts the device into the stopped state. Don't call mal_device__set_state() here.
mal_event_wait(&pDevice->stopEvent);
return MAL_SUCCESS;
} }
mal_bool32 mal_device_is_started(mal_device* pDevice) mal_bool32 mal_device_is_started(mal_device* pDevice)
{ {
if (pDevice == NULL) return MAL_FALSE; if (pDevice == NULL) return MAL_FALSE;
return (pDevice->flags & MAL_FLAG_STARTED) != 0; return mal_device__get_state(pDevice) == MAL_STATE_STARTED;
} }
mal_uint32 mal_device_get_fragment_size_in_bytes(mal_device* pDevice) mal_uint32 mal_device_get_fragment_size_in_bytes(mal_device* pDevice)
...@@ -1886,26 +2275,38 @@ mal_uint32 mal_get_sample_size_in_bytes(mal_format format) ...@@ -1886,26 +2275,38 @@ mal_uint32 mal_get_sample_size_in_bytes(mal_format format)
// TODO // TODO
// ==== // ====
// - Error handling in worker threads isn't quite right. At the top of the main loop, before the semaphore wait, I think
// the device needs to be unmarked as playing.
// - mal_device_start() and mal_device_stop() need improving:
// - Need to wait for a synchronization primitive on the worker thread to signal that the operation is complete.
// - Need a more accurate return code.
// - Make starting and stopping thread-safe
// - More error codes // - More error codes
// - Logging // - Logging
// - Profiling. Need to measure mal_device_start() and mal_device_stop() in particular. One of the two seems to be taking a bit
// longer than it should.
// - Initial test for start/stop times show that it's _not_ tied to the fragment size...
// - Implement mmap mode for ALSA. // - Implement mmap mode for ALSA.
// - Make device initialization more robust for ALSA // - Make device initialization more robust for ALSA
// - Clamp period sizes to their min/max. // - Clamp period sizes to their min/max.
// - Support rewinding. This will enable applications to employ better anti-latency. // - Support rewinding. This will enable applications to employ better anti-latency.
// - [DirectSound] When a device is stopped, none of the samples in the current fragment are sent to the client.
// - Implement the null device.
// DEVELOPMENT NOTES // DEVELOPMENT NOTES
// ================= // =================
// //
// General
// -------
// - An "event" is just a binary semaphore and is the only synchronization primitive used by mini_al. An event is
// always auto-reset and initially unsignaled.
//
//
// ALSA // ALSA
// ---- // ----
// - [DONE] Use snd_pcm_recover() when snd_pcm_writei() or snd_pcm_readi() fails. // - [DONE] Use snd_pcm_recover() when snd_pcm_writei() or snd_pcm_readi() fails.
//
//
// Synchronization
// ---------------
// - Need to use an event (or binary semaphore) instead of a regular semaphore. The reason for this is that we never want
// any of the semaphores to get set to a value greater than 1, but this is not directly supported by posix. This becomes
// an issue with stopping in particular. See http://stackoverflow.com/questions/7478684/how-to-initialise-a-binary-semaphore-in-c
/* /*
......
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