Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
M
miniaudio
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Locked Files
Issues
0
Issues
0
List
Boards
Labels
Service Desk
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Security & Compliance
Security & Compliance
Dependency List
License Compliance
Packages
Packages
List
Container Registry
Analytics
Analytics
CI / CD
Code Review
Insights
Issues
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
MyCard
miniaudio
Commits
d2920b18
Commit
d2920b18
authored
Oct 14, 2016
by
David Reid
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Initial commit.
parents
Changes
2
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
2028 additions
and
0 deletions
+2028
-0
README.md
README.md
+91
-0
mini_al.h
mini_al.h
+1937
-0
No files found.
README.md
0 → 100644
View file @
d2920b18
mini_al - Mini Audio Library
============================
mini_al is a simple library for playing and recording audio. It's focused on simplicity and has
a very small number of APIs. This is not a full-featured audio library, nor will it ever be. It
is intended to be used as a quick and easy way to connect to an audio device and deliver and
capture audio data from speakers and microphones.
C/C++, single file, public domain.
Features
========
-
A very simple API.
-
Both synchronous and asynchronous APIs.
Examples
========
Asynchonous API
---------------
In asynchronous mode, mini_al will request and deliver audio data through callbacks.
```
mal_uint32 on_send_audio_data(mal_device* pDevice, mal_uint32 sampleCount, void* pSamples)
{
return drwav_read_f32((drwav*)pDevice->pUserData, sampleCount, pSamples);
}
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(&playbackDevice, mal_device_type_playback, NULL, mal_format_f32, channels, sampleRate, fragmentSizeInFrames, fragmentCount) != MAL_SUCCESS) {
return -1;
}
mal_device_set_send_callback(&playbackDevice, on_send_audio_data);
mal_device_start(&playbackDevice);
printf("Press Enter to quit...\n");
getchar();
mal_device_uninit(&playbackDevice);
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
mini_al.h
0 → 100644
View file @
d2920b18
// Mini audio library. Public domain. See "unlicense" statement at the end of this file.
// mini_al - v0.0 - UNRELEASED
//
// David Reid - mackron@gmail.com
// USAGE
// =====
//
// (WRITE ME)
//
//
// NOTES
// =====
// - 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.
// - This is not currently thread-safe, but can still be used from multiple threads if you do your own
// synchronization.
#ifndef dr_mal_h
#define dr_mal_h
#ifdef __cplusplus
extern
"C"
{
#endif
// Platform/backend detection.
#ifdef _WIN32
#define MAL_WIN32
#ifndef MAL_NO_DSOUND
#define MAL_ENABLE_DSOUND
#endif
#else
#define MAL_POSIX
#if !defined(MAL_NO_ALSA) && defined(__linux__)
#define MAL_ENABLE_ALSA
#endif
// Unfortunate #includes, but needed for pthread_t and sem_t types.
#include <pthread.h>
#include <semaphore.h>
#endif
#ifndef MAL_NO_NULL
#define MAL_ENABLE_NULL
#endif
#if defined(_MSC_VER) && _MSC_VER < 1600
typedef
signed
char
mal_int8
;
typedef
unsigned
char
mal_uint8
;
typedef
signed
short
mal_int16
;
typedef
unsigned
short
mal_uint16
;
typedef
signed
int
mal_int32
;
typedef
unsigned
int
mal_uint32
;
typedef
signed
__int64
mal_int64
;
typedef
unsigned
__int64
mal_uint64
;
#else
#include <stdint.h>
typedef
int8_t
mal_int8
;
typedef
uint8_t
mal_uint8
;
typedef
int16_t
mal_int16
;
typedef
uint16_t
mal_uint16
;
typedef
int32_t
mal_int32
;
typedef
uint32_t
mal_uint32
;
typedef
int64_t
mal_int64
;
typedef
uint64_t
mal_uint64
;
#endif
typedef
mal_int8
mal_bool8
;
typedef
mal_int32
mal_bool32
;
#define MAL_TRUE 1
#define MAL_FALSE 0
typedef
void
*
mal_handle
;
typedef
void
*
mal_ptr
;
#ifdef MAL_WIN32
typedef
mal_handle
mal_thread
;
typedef
mal_handle
mal_semaphore
;
#else
typedef
pthread_t
mal_thread
;
typedef
sem_t
mal_semaphore
;
#endif
#ifdef MAL_ENABLE_DSOUND
#define MAL_MAX_FRAGMENTS_DSOUND 16
#endif
typedef
int
mal_result
;
#define MAL_SUCCESS 0
#define MAL_UNKNOWN_ERROR -1
#define MAL_INVALID_ARGS -2
#define MAL_OUT_OF_MEMORY -3
#define MAL_NO_BACKEND -16
#define MAL_DEVICE_ALREADY_STARTED -17
#define MAL_DEVICE_ALREADY_STOPPED -18
#define MAL_FAILED_TO_INIT_BACKEND -19
#define MAL_FORMAT_NOT_SUPPORTED -20
typedef
struct
mal_device
mal_device
;
typedef
void
(
*
mal_recv_proc
)(
mal_device
*
pDevice
,
mal_uint32
sampleCount
,
const
void
*
pSamples
);
typedef
mal_uint32
(
*
mal_send_proc
)(
mal_device
*
pDevice
,
mal_uint32
sampleCount
,
void
*
pSamples
);
typedef
enum
{
mal_api_null
,
mal_api_dsound
,
mal_api_alsa
}
mal_api
;
typedef
enum
{
mal_device_type_playback
,
mal_device_type_capture
}
mal_device_type
;
typedef
enum
{
// I like to keep these explicitly defined because they're used as a key into a lookup table.
mal_format_u8
=
0
,
mal_format_s16
=
1
,
mal_format_s24
=
2
,
mal_format_s32
=
3
,
mal_format_f32
=
4
,
mal_format_f64
=
5
,
mal_format_alaw
=
6
,
mal_format_mulaw
=
7
}
mal_format
;
typedef
union
{
char
name
[
256
];
// ALSA uses a name string for identification.
mal_uint8
guid
[
16
];
// DirectSound uses a GUID to identify a device.
}
mal_device_id
;
typedef
struct
{
mal_device_id
id
;
char
description
[
256
];
}
mal_device_info
;
struct
mal_device
{
mal_api
api
;
// DirectSound, ALSA, etc.
mal_device_type
type
;
mal_format
format
;
mal_uint32
channels
;
mal_uint32
sampleRate
;
mal_uint32
fragmentSizeInFrames
;
mal_uint32
fragmentCount
;
mal_uint32
flags
;
mal_recv_proc
onRecv
;
mal_send_proc
onSend
;
void
*
pUserData
;
// Application defined data.
union
{
#ifdef MAL_ENABLE_DSOUND
struct
{
/*HMODULE*/
mal_handle
hDSoundDLL
;
/*LPDIRECTSOUND8*/
mal_ptr
pPlayback
;
/*LPDIRECTSOUNDBUFFER*/
mal_ptr
pPlaybackPrimaryBuffer
;
/*LPDIRECTSOUNDBUFFER*/
mal_ptr
pPlaybackBuffer
;
/*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
hStopEvent
;
mal_thread
thread
;
mal_semaphore
semaphore
;
// <-- This is used to wake up the worker thread.
}
dsound
;
#endif
#ifdef MAL_ENABLE_ALSA
struct
{
/*snd_pcm_t**/
mal_ptr
pPCM
;
mal_thread
thread
;
mal_semaphore
semaphore
;
// <-- This is used to wake up the thread.
void
*
pIntermediaryBuffer
;
}
alsa
;
#endif
#ifdef MAL_ENABLE_NULL
struct
{
int
unused
;
}
null_device
;
#endif
};
};
// Enumerates over each device of the given type (playback or capture).
//
// Thread Safety: SAFE, SEE NOTES.
// 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.
//
// Efficiency: SLOW
// 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
);
// Initializes a device.
//
// 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
// information.
//
// This will try it's hardest to create a valid device, even if it means adjusting input arguments.
// Use pDevice->channels, pDevice->sampleRate, etc. to determine the actual properties after
// initialization.
//
// Thread Safety: SAFE
// This API is thread safe so long as the application does not try to use the device object before
// this call has returned.
//
// Efficiency: 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.
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
);
// Uninitializes a device.
//
// This will explicitly stop the device. You do not need to call mal_device_stop() beforehand, but it's
// harmless if you do.
//
// Thread Safety: UNSAFE
// 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.
//
// Efficiency: SLOW
// 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.
void
mal_device_uninit
(
mal_device
*
pDevice
);
// Sets the callback to use when the application has receives data from the device.
//
// Thread Safety: SAFE
// This API is implemented as a simple atomic assignment.
//
// Efficiency: FAST
// This is just an atomic assignment.
void
mal_device_set_recv_callback
(
mal_device
*
pDevice
,
mal_recv_proc
proc
);
// Sets the callback to use when the application needs to send data to the device for playback.
//
// Thread Safety: SAFE
// This API is implemented as a simple atomic assignment.
//
// Efficiency: FAST
// This is just an atomic assignment.
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
// recording.
//
// Thread Safety: SAFE
//
// Efficiency: SLOW
// This API needs to wait on the worker thread via a semaphore.
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.
//
// Thread Safety: SAFE
//
// Efficiency: SLOW
// 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
);
// Determines whether or not the device is started.
//
// Thread Safety: SAFE
// 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.
//
// Efficiency: FAST
// This is implemented with a simple accessor.
mal_bool32
mal_device_is_started
(
mal_device
*
pDevice
);
// Retrieves the size of a fragment 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: FAST
// This is implemented with just a few 32-bit integer multiplications.
mal_uint32
mal_device_get_fragment_size_in_bytes
(
mal_device
*
pDevice
);
// Retrieves the size of a sample in bytes for the given format.
//
// Thread Safety: SAFE
// This is API is pure.
//
// Efficiency: FAST
// This is implemented with a lookup table.
mal_uint32
mal_get_sample_size_in_bytes
(
mal_format
format
);
#ifdef __cplusplus
}
#endif
#endif //dr_mal_h
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//
// IMPLEMENTATION
//
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
#ifdef MAL_IMPLEMENTATION
#ifdef MAL_WIN32
#include <windows.h>
#else
#include <string.h> // For memset()
#endif
#ifdef MAL_POSIX
#include <pthread.h>
#include <semaphore.h>
#endif
#include <assert.h>
#include <errno.h>
#include <stdio.h> // For printf() debugging. TODO: Delete this and replace with a proper logging system.
#ifdef MAL_WIN32
#define MAL_THREADCALL WINAPI
typedef
mal_uint32
mal_thread_result
;
#else
#define MAL_THREADCALL
typedef
void
*
mal_thread_result
;
#endif
typedef
mal_thread_result
(
MAL_THREADCALL
*
mal_thread_entry_proc
)(
void
*
pData
);
#define MAL_FLAG_INITIALIZED (1 << 0)
#define MAL_FLAG_AWAKE (1 << 1)
#define MAL_FLAG_STARTED (1 << 2) // Whether or not the device is currently awake and running.
#define MAL_FLAG_TERMINATING (1 << 3) // Used for thread management.
///////////////////////////////////////////////////////////////////////////////
//
// Standard Library Stuff
//
///////////////////////////////////////////////////////////////////////////////
#ifndef mal_zero_memory
#ifdef MAL_WIN32
#define mal_zero_memory(p, sz) ZeroMemory((p), (sz))
#else
#define mal_zero_memory(p, sz) memset((p), 0, (sz))
#endif
#endif
#define mal_zero_object(p) mal_zero_memory((p), sizeof(*(p)))
#ifndef mal_copy_memory
#ifdef _WIN32
#define mal_copy_memory(dst, src, sz) CopyMemory((dst), (src), (sz))
#else
#define mal_copy_memory(dst, src, sz) memcpy((dst), (src), (sz))
#endif
#endif
#ifndef mal_malloc
#ifdef MAL_WIN32
#define mal_malloc(sz) HeapAlloc(GetProcessHeap(), 0, (sz))
#else
#define mal_malloc(sz) malloc((sz))
#endif
#endif
#ifndef mal_free
#ifdef MAL_WIN32
#define mal_free(p) HeapFree(GetProcessHeap(), 0, (p))
#else
#define mal_free(p) free((p))
#endif
#endif
#ifndef mal_assert
#ifdef MAL_WIN32
#define mal_assert(condition) assert(condition)
#else
#define mal_assert(condition) assert(condition)
#endif
#endif
static
int
mal_strncpy_s
(
char
*
dst
,
size_t
dstSizeInBytes
,
const
char
*
src
,
size_t
count
)
{
if
(
dst
==
0
)
{
return
EINVAL
;
}
if
(
dstSizeInBytes
==
0
)
{
return
EINVAL
;
}
if
(
src
==
0
)
{
dst
[
0
]
=
'\0'
;
return
EINVAL
;
}
size_t
maxcount
=
count
;
if
(
count
==
((
size_t
)
-
1
)
||
count
>=
dstSizeInBytes
)
{
// -1 = _TRUNCATE
maxcount
=
dstSizeInBytes
-
1
;
}
size_t
i
;
for
(
i
=
0
;
i
<
maxcount
&&
src
[
i
]
!=
'\0'
;
++
i
)
{
dst
[
i
]
=
src
[
i
];
}
if
(
src
[
i
]
==
'\0'
||
i
==
count
||
count
==
((
size_t
)
-
1
))
{
dst
[
i
]
=
'\0'
;
return
0
;
}
dst
[
0
]
=
'\0'
;
return
ERANGE
;
}
// Thanks to good old Bit Twiddling Hacks for this one: http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
static
unsigned
int
mal_next_power_of_2
(
unsigned
int
x
)
{
x
--
;
x
|=
x
>>
1
;
x
|=
x
>>
2
;
x
|=
x
>>
4
;
x
|=
x
>>
8
;
x
|=
x
>>
16
;
x
++
;
return
x
;
}
///////////////////////////////////////////////////////////////////////////////
//
// Threading
//
///////////////////////////////////////////////////////////////////////////////
#ifdef MAL_WIN32
mal_bool32
mal_thread_create__win32
(
mal_thread
*
pThread
,
mal_thread_entry_proc
entryProc
,
void
*
pData
)
{
*
pThread
=
CreateThread
(
NULL
,
0
,
entryProc
,
pData
,
0
,
NULL
);
if
(
*
pThread
==
NULL
)
{
return
MAL_FALSE
;
}
return
MAL_TRUE
;
}
void
mal_thread_wait__win32
(
mal_thread
*
pThread
)
{
WaitForSingleObject
(
*
pThread
,
INFINITE
);
}
mal_bool32
mal_semaphore_create__win32
(
mal_semaphore
*
pSemaphore
,
int
initialValue
)
{
*
pSemaphore
=
CreateSemaphoreA
(
NULL
,
initialValue
,
LONG_MAX
,
NULL
);
if
(
*
pSemaphore
==
NULL
)
{
return
MAL_FALSE
;
}
return
MAL_TRUE
;
}
void
mal_semaphore_delete__win32
(
mal_semaphore
*
pSemaphore
)
{
CloseHandle
(
*
pSemaphore
);
}
mal_bool32
mal_semaphore_wait__win32
(
mal_semaphore
*
pSemaphore
)
{
return
WaitForSingleObject
(
*
pSemaphore
,
INFINITE
)
==
WAIT_OBJECT_0
;
}
mal_bool32
mal_semaphore_release__win32
(
mal_semaphore
*
pSemaphore
)
{
return
ReleaseSemaphore
(
*
pSemaphore
,
1
,
NULL
)
!=
0
;
}
#endif
#ifdef MAL_POSIX
mal_bool32
mal_thread_create__posix
(
mal_thread
*
pThread
,
mal_thread_entry_proc
entryProc
,
void
*
pData
)
{
return
pthread_create
(
pThread
,
NULL
,
entryProc
,
pData
)
==
0
;
}
void
mal_thread_wait__posix
(
mal_thread
*
pThread
)
{
pthread_join
(
*
pThread
,
NULL
);
}
mal_bool32
mal_semaphore_create__posix
(
mal_semaphore
*
pSemaphore
,
int
initialValue
)
{
return
sem_init
(
pSemaphore
,
0
,
(
unsigned
int
)
initialValue
)
!=
-
1
;
}
void
mal_semaphore_delete__posix
(
mal_semaphore
*
pSemaphore
)
{
sem_close
(
pSemaphore
);
}
mal_bool32
mal_semaphore_wait__posix
(
mal_semaphore
*
pSemaphore
)
{
return
sem_wait
(
pSemaphore
)
!=
-
1
;
}
mal_bool32
mal_semaphore_release__posix
(
mal_semaphore
*
pSemaphore
)
{
return
sem_post
(
pSemaphore
)
!=
-
1
;
}
#endif
mal_bool32
mal_thread_create
(
mal_thread
*
pThread
,
mal_thread_entry_proc
entryProc
,
void
*
pData
)
{
if
(
pThread
==
NULL
||
entryProc
==
NULL
)
return
MAL_FALSE
;
#ifdef MAL_WIN32
return
mal_thread_create__win32
(
pThread
,
entryProc
,
pData
);
#endif
#ifdef MAL_POSIX
return
mal_thread_create__posix
(
pThread
,
entryProc
,
pData
);
#endif
}
void
mal_thread_wait
(
mal_thread
*
pThread
)
{
if
(
pThread
==
NULL
)
return
;
#ifdef MAL_WIN32
mal_thread_wait__win32
(
pThread
);
#endif
#ifdef MAL_POSIX
mal_thread_wait__posix
(
pThread
);
#endif
}
mal_bool32
mal_semaphore_create
(
mal_semaphore
*
pSemaphore
,
int
initialValue
)
{
if
(
pSemaphore
==
NULL
)
return
MAL_FALSE
;
#ifdef MAL_WIN32
return
mal_semaphore_create__win32
(
pSemaphore
,
initialValue
);
#endif
#ifdef MAL_POSIX
return
mal_semaphore_create__posix
(
pSemaphore
,
initialValue
);
#endif
}
void
mal_semaphore_delete
(
mal_semaphore
*
pSemaphore
)
{
if
(
pSemaphore
==
NULL
)
return
;
#ifdef MAL_WIN32
mal_semaphore_delete__win32
(
pSemaphore
);
#endif
#ifdef MAL_POSIX
mal_semaphore_delete__posix
(
pSemaphore
);
#endif
}
mal_bool32
mal_semaphore_wait
(
mal_semaphore
*
pSemaphore
)
{
if
(
pSemaphore
==
NULL
)
return
MAL_FALSE
;
#ifdef MAL_WIN32
return
mal_semaphore_wait__win32
(
pSemaphore
);
#endif
#ifdef MAL_POSIX
return
mal_semaphore_wait__posix
(
pSemaphore
);
#endif
}
mal_bool32
mal_semaphore_release
(
mal_semaphore
*
pSemaphore
)
{
if
(
pSemaphore
==
NULL
)
return
MAL_FALSE
;
#ifdef MAL_WIN32
return
mal_semaphore_release__win32
(
pSemaphore
);
#endif
#ifdef MAL_POSIX
return
mal_semaphore_release__posix
(
pSemaphore
);
#endif
}
// 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.
static
inline
mal_uint32
mal_device__read_fragment_from_client
(
mal_device
*
pDevice
,
mal_uint32
sampleCount
,
void
*
pSamples
)
{
mal_assert
(
pDevice
!=
NULL
);
mal_assert
(
sampleCount
>
0
);
mal_assert
(
pSamples
!=
NULL
);
mal_uint32
samplesRead
=
0
;
if
(
pDevice
->
onSend
)
{
samplesRead
=
pDevice
->
onSend
(
pDevice
,
sampleCount
,
pSamples
);
}
mal_uint32
sampleSize
=
mal_get_sample_size_in_bytes
(
pDevice
->
format
);
mal_uint32
consumedBytes
=
samplesRead
*
sampleSize
;
mal_uint32
remainingBytes
=
(
sampleCount
-
samplesRead
)
*
sampleSize
;
mal_zero_memory
((
mal_uint8
*
)
pSamples
+
consumedBytes
,
remainingBytes
);
return
samplesRead
;
}
// 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
)
{
mal_assert
(
pDevice
!=
NULL
);
mal_assert
(
sampleCount
>
0
);
mal_assert
(
pSamples
!=
NULL
);
if
(
pDevice
->
onRecv
)
{
pDevice
->
onRecv
(
pDevice
,
sampleCount
,
pSamples
);
}
}
///////////////////////////////////////////////////////////////////////////////
//
// Null Backend
//
///////////////////////////////////////////////////////////////////////////////
#ifdef MAL_ENABLE_NULL
mal_result
mal_enumerate_devices__null
(
mal_device_type
type
,
mal_uint32
*
pCount
,
mal_device_info
*
pInfo
)
{
mal_uint32
infoSize
=
*
pCount
;
*
pCount
=
1
;
// There's only one "device" each for playback and recording for the null backend.
if
(
pInfo
!=
NULL
&&
infoSize
>
0
)
{
mal_zero_memory
(
&
pInfo
->
id
,
sizeof
(
pInfo
->
id
));
if
(
type
==
mal_device_type_playback
)
{
mal_strncpy_s
(
pInfo
->
description
,
sizeof
(
pInfo
->
description
),
"NULL Playback Device"
,
(
size_t
)
-
1
);
}
else
{
mal_strncpy_s
(
pInfo
->
description
,
sizeof
(
pInfo
->
description
),
"NULL Capture Device"
,
(
size_t
)
-
1
);
}
}
return
MAL_SUCCESS
;
}
void
mal_device_uninit__null
(
mal_device
*
pDevice
)
{
mal_assert
(
pDevice
!=
NULL
);
}
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
)
{
mal_assert
(
pDevice
!=
NULL
);
pDevice
->
api
=
mal_api_null
;
// TODO: Implement me.
return
MAL_NO_BACKEND
;
}
mal_result
mal_device_start__null
(
mal_device
*
pDevice
)
{
mal_assert
(
pDevice
!=
NULL
);
return
MAL_UNKNOWN_ERROR
;
}
mal_result
mal_device_stop__null
(
mal_device
*
pDevice
)
{
mal_assert
(
pDevice
!=
NULL
);
return
MAL_UNKNOWN_ERROR
;
}
#endif
///////////////////////////////////////////////////////////////////////////////
//
// DirectSound Backend
//
///////////////////////////////////////////////////////////////////////////////
#ifdef MAL_ENABLE_DSOUND
#include <dsound.h>
#include <mmreg.h> // WAVEFORMATEX
static
GUID
MAL_GUID_NULL
=
{
0x00000000
,
0x0000
,
0x0000
,
{
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
}};
static
GUID
_g_mal_GUID_IID_DirectSoundNotify
=
{
0xb0210783
,
0x89cd
,
0x11d0
,
{
0xaf
,
0x08
,
0x00
,
0xa0
,
0xc9
,
0x25
,
0xcd
,
0x16
}};
static
GUID
_g_mal_GUID_IID_IDirectSoundCaptureBuffer8
=
{
0x00990df4
,
0x0dbb
,
0x4872
,
{
0x83
,
0x3e
,
0x6d
,
0x30
,
0x3e
,
0x80
,
0xae
,
0xb6
}};
static
GUID
_g_mal_GUID_KSDATAFORMAT_SUBTYPE_PCM
=
{
0x00000001
,
0x0000
,
0x0010
,
{
0x80
,
0x00
,
0x00
,
0xaa
,
0x00
,
0x38
,
0x9b
,
0x71
}};
static
GUID
_g_mal_GUID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
=
{
0x00000003
,
0x0000
,
0x0010
,
{
0x80
,
0x00
,
0x00
,
0xaa
,
0x00
,
0x38
,
0x9b
,
0x71
}};
static
GUID
_g_mal_GUID_KSDATAFORMAT_SUBTYPE_ALAW
=
{
0x00000006
,
0x0000
,
0x0010
,
{
0x80
,
0x00
,
0x00
,
0xaa
,
0x00
,
0x38
,
0x9b
,
0x71
}};
static
GUID
_g_mal_GUID_KSDATAFORMAT_SUBTYPE_MULAW
=
{
0x00000007
,
0x0000
,
0x0010
,
{
0x80
,
0x00
,
0x00
,
0xaa
,
0x00
,
0x38
,
0x9b
,
0x71
}};
#ifdef __cplusplus
static
GUID
g_mal_GUID_IID_DirectSoundNotify
=
_g_mal_GUID_IID_DirectSoundNotify
;
static
GUID
g_mal_GUID_IID_IDirectSoundCaptureBuffer8
=
_g_mal_GUID_IID_IDirectSoundCaptureBuffer8
;
#else
static
GUID
*
g_mal_GUID_IID_DirectSoundNotify
=
&
_g_mal_GUID_IID_DirectSoundNotify
;
static
GUID
*
g_mal_GUID_IID_IDirectSoundCaptureBuffer8
=
&
_g_mal_GUID_IID_IDirectSoundCaptureBuffer8
;
#endif
typedef
HRESULT
(
WINAPI
*
mal_DirectSoundCreate8Proc
)(
LPCGUID
pcGuidDevice
,
LPDIRECTSOUND8
*
ppDS8
,
LPUNKNOWN
pUnkOuter
);
typedef
HRESULT
(
WINAPI
*
mal_DirectSoundEnumerateAProc
)(
LPDSENUMCALLBACKA
pDSEnumCallback
,
LPVOID
pContext
);
typedef
HRESULT
(
WINAPI
*
mal_DirectSoundCaptureCreate8Proc
)(
LPCGUID
pcGuidDevice
,
LPDIRECTSOUNDCAPTURE8
*
ppDSC8
,
LPUNKNOWN
pUnkOuter
);
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
()
{
return
LoadLibraryW
(
L"dsound.dll"
);
}
static
void
mal_close_dsound_dll
(
HMODULE
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
{
mal_uint32
deviceCount
;
mal_uint32
infoCount
;
mal_device_info
*
pInfo
;
}
mal_device_enum_data__dsound
;
static
BOOL
CALLBACK
mal_enum_devices_callback__dsound
(
LPGUID
lpGuid
,
LPCSTR
lpcstrDescription
,
LPCSTR
lpcstrModule
,
LPVOID
lpContext
)
{
(
void
)
lpcstrModule
;
mal_device_enum_data__dsound
*
pData
=
(
mal_device_enum_data__dsound
*
)
lpContext
;
assert
(
pData
!=
NULL
);
if
(
pData
->
pInfo
!=
NULL
)
{
if
(
pData
->
infoCount
>
0
)
{
mal_strncpy_s
(
pData
->
pInfo
->
description
,
sizeof
(
pData
->
pInfo
->
description
),
lpcstrDescription
,
(
size_t
)
-
1
);
if
(
lpGuid
!=
NULL
)
{
mal_copy_memory
(
pData
->
pInfo
->
id
.
guid
,
lpGuid
,
16
);
}
else
{
mal_zero_memory
(
pData
->
pInfo
->
id
.
guid
,
16
);
}
pData
->
pInfo
+=
1
;
pData
->
infoCount
-=
1
;
pData
->
deviceCount
+=
1
;
}
}
else
{
pData
->
deviceCount
+=
1
;
}
return
TRUE
;
}
mal_result
mal_enumerate_devices__dsound
(
mal_device_type
type
,
mal_uint32
*
pCount
,
mal_device_info
*
pInfo
)
{
mal_uint32
infoSize
=
*
pCount
;
*
pCount
=
0
;
mal_device_enum_data__dsound
enumData
;
enumData
.
deviceCount
=
0
;
enumData
.
infoCount
=
infoSize
;
enumData
.
pInfo
=
pInfo
;
HMODULE
dsoundDLL
=
mal_open_dsound_dll
();
if
(
dsoundDLL
==
NULL
)
{
return
MAL_NO_BACKEND
;
}
if
(
type
==
mal_device_type_playback
)
{
mal_DirectSoundEnumerateAProc
pDirectSoundEnumerateA
=
(
mal_DirectSoundEnumerateAProc
)
GetProcAddress
(
dsoundDLL
,
"DirectSoundEnumerateA"
);
if
(
pDirectSoundEnumerateA
)
{
pDirectSoundEnumerateA
(
mal_enum_devices_callback__dsound
,
&
enumData
);
}
}
else
{
mal_DirectSoundCaptureEnumerateAProc
pDirectSoundCaptureEnumerateA
=
(
mal_DirectSoundCaptureEnumerateAProc
)
GetProcAddress
(
dsoundDLL
,
"DirectSoundCaptureEnumerateA"
);
if
(
pDirectSoundCaptureEnumerateA
)
{
pDirectSoundCaptureEnumerateA
(
mal_enum_devices_callback__dsound
,
&
enumData
);
}
}
mal_close_dsound_dll
(
dsoundDLL
);
*
pCount
=
enumData
.
deviceCount
;
return
MAL_SUCCESS
;
}
void
mal_device_uninit__dsound
(
mal_device
*
pDevice
)
{
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
.
hStopEvent
)
{
CloseHandle
(
pDevice
->
dsound
.
hStopEvent
);
}
for
(
mal_uint32
i
=
0
;
i
<
pDevice
->
fragmentCount
;
++
i
)
{
if
(
pDevice
->
dsound
.
pNotifyEvents
[
i
])
{
CloseHandle
(
pDevice
->
dsound
.
pNotifyEvents
[
i
]);
}
}
if
(
pDevice
->
dsound
.
pCaptureBuffer
)
{
IDirectSoundCaptureBuffer8_Release
((
LPDIRECTSOUNDBUFFER8
)
pDevice
->
dsound
.
pCaptureBuffer
);
}
if
(
pDevice
->
dsound
.
pCapture
)
{
IDirectSoundCapture_Release
((
LPDIRECTSOUNDCAPTURE8
)
pDevice
->
dsound
.
pCaptureBuffer
);
}
if
(
pDevice
->
dsound
.
pPlaybackBuffer
)
{
IDirectSoundBuffer_Release
((
LPDIRECTSOUNDBUFFER
)
pDevice
->
dsound
.
pPlaybackBuffer
);
}
if
(
pDevice
->
dsound
.
pPlaybackPrimaryBuffer
)
{
IDirectSoundBuffer_Release
((
LPDIRECTSOUNDBUFFER
)
pDevice
->
dsound
.
pPlaybackPrimaryBuffer
);
}
if
(
pDevice
->
dsound
.
pPlayback
!=
NULL
)
{
IDirectSound_Release
((
LPDIRECTSOUND8
)
pDevice
->
dsound
.
pPlayback
);
}
mal_close_dsound_dll
((
HMODULE
)
pDevice
->
dsound
.
hDSoundDLL
);
}
}
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
)
{
mal_assert
(
pDevice
!=
NULL
);
pDevice
->
api
=
mal_api_dsound
;
pDevice
->
dsound
.
hDSoundDLL
=
(
mal_handle
)
mal_open_dsound_dll
();
if
(
pDevice
->
dsound
.
hDSoundDLL
==
NULL
)
{
return
MAL_NO_BACKEND
;
}
// Check that we have a valid format.
GUID
subformat
;
switch
(
format
)
{
case
mal_format_u8
:
case
mal_format_s16
:
case
mal_format_s32
:
{
subformat
=
_g_mal_GUID_KSDATAFORMAT_SUBTYPE_PCM
;
}
break
;
case
mal_format_f32
:
case
mal_format_f64
:
{
subformat
=
_g_mal_GUID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
;
}
break
;
case
mal_format_alaw
:
{
subformat
=
_g_mal_GUID_KSDATAFORMAT_SUBTYPE_ALAW
;
}
break
;
case
mal_format_mulaw
:
{
subformat
=
_g_mal_GUID_KSDATAFORMAT_SUBTYPE_MULAW
;
}
break
;
case
mal_format_s24
:
// <-- Untested. Not quite sure on alignment.
default:
return
MAL_FORMAT_NOT_SUPPORTED
;
}
if
(
fragmentCount
>
MAL_MAX_FRAGMENTS_DSOUND
)
{
fragmentCount
=
MAL_MAX_FRAGMENTS_DSOUND
;
}
WAVEFORMATEXTENSIBLE
wf
;
mal_zero_object
(
&
wf
);
wf
.
Format
.
cbSize
=
sizeof
(
wf
);
wf
.
Format
.
wFormatTag
=
WAVE_FORMAT_EXTENSIBLE
;
wf
.
Format
.
nChannels
=
(
WORD
)
channels
;
wf
.
Format
.
nSamplesPerSec
=
(
DWORD
)
sampleRate
;
wf
.
Format
.
wBitsPerSample
=
mal_get_sample_size_in_bytes
(
format
)
*
8
;
wf
.
Format
.
nBlockAlign
=
(
wf
.
Format
.
nChannels
*
wf
.
Format
.
wBitsPerSample
)
/
8
;
wf
.
Format
.
nAvgBytesPerSec
=
wf
.
Format
.
nBlockAlign
*
wf
.
Format
.
nSamplesPerSec
;
wf
.
Samples
.
wValidBitsPerSample
=
wf
.
Format
.
wBitsPerSample
;
wf
.
dwChannelMask
=
(
channels
<=
2
)
?
0
:
~
(((
DWORD
)
-
1
)
<<
channels
);
wf
.
SubFormat
=
subformat
;
DWORD
fragmentSizeInBytes
=
mal_device_get_fragment_size_in_bytes
(
pDevice
);
// Unfortunately DirectSound uses different APIs and data structures for playback and catpure devices :(
if
(
type
==
mal_device_type_playback
)
{
mal_DirectSoundCreate8Proc
pDirectSoundCreate8
=
(
mal_DirectSoundCreate8Proc
)
GetProcAddress
(
pDevice
->
dsound
.
hDSoundDLL
,
"DirectSoundCreate8"
);
if
(
pDirectSoundCreate8
==
NULL
)
{
mal_device_uninit__dsound
(
pDevice
);
return
MAL_NO_BACKEND
;
}
if
(
FAILED
(
pDirectSoundCreate8
((
pDeviceID
==
NULL
)
?
NULL
:
(
LPCGUID
)
pDeviceID
->
guid
,
(
LPDIRECTSOUND8
*
)
&
pDevice
->
dsound
.
pPlayback
,
NULL
)))
{
mal_device_uninit__dsound
(
pDevice
);
return
MAL_NO_BACKEND
;
}
// The cooperative level must be set before doing anything else.
if
(
FAILED
(
IDirectSound_SetCooperativeLevel
((
LPDIRECTSOUND8
)
pDevice
->
dsound
.
pPlayback
,
GetForegroundWindow
(),
DSSCL_PRIORITY
)))
{
mal_device_uninit__dsound
(
pDevice
);
return
MAL_NO_BACKEND
;
}
DSBUFFERDESC
descDSPrimary
;
mal_zero_object
(
&
descDSPrimary
);
descDSPrimary
.
dwSize
=
sizeof
(
DSBUFFERDESC
);
descDSPrimary
.
dwFlags
=
DSBCAPS_PRIMARYBUFFER
|
DSBCAPS_CTRLVOLUME
;
if
(
FAILED
(
IDirectSound_CreateSoundBuffer
((
LPDIRECTSOUND8
)
pDevice
->
dsound
.
pPlayback
,
&
descDSPrimary
,
(
LPDIRECTSOUNDBUFFER
*
)
&
pDevice
->
dsound
.
pPlaybackPrimaryBuffer
,
NULL
)))
{
mal_device_uninit__dsound
(
pDevice
);
return
MAL_NO_BACKEND
;
}
// From MSDN:
//
// The method succeeds even if the hardware does not support the requested format; DirectSound sets the buffer to the closest
// supported format. To determine whether this has happened, an application can call the GetFormat method for the primary buffer
// and compare the result with the format that was requested with the SetFormat method.
if
(
FAILED
(
IDirectSoundBuffer_SetFormat
((
LPDIRECTSOUNDBUFFER
)
pDevice
->
dsound
.
pPlaybackPrimaryBuffer
,
(
WAVEFORMATEX
*
)
&
wf
)))
{
mal_device_uninit__dsound
(
pDevice
);
return
MAL_NO_BACKEND
;
}
// Get the _actual_ properties of the buffer. This is silly API design...
DWORD
requiredSize
;
if
(
FAILED
(
IDirectSoundBuffer_GetFormat
((
LPDIRECTSOUNDBUFFER
)
pDevice
->
dsound
.
pPlaybackPrimaryBuffer
,
NULL
,
0
,
&
requiredSize
)))
{
mal_device_uninit__dsound
(
pDevice
);
return
MAL_NO_BACKEND
;
}
char
rawdata
[
1024
];
WAVEFORMATEXTENSIBLE
*
pActualFormat
=
(
WAVEFORMATEXTENSIBLE
*
)
rawdata
;
if
(
FAILED
(
IDirectSoundBuffer_GetFormat
((
LPDIRECTSOUNDBUFFER
)
pDevice
->
dsound
.
pPlaybackPrimaryBuffer
,
(
WAVEFORMATEX
*
)
pActualFormat
,
requiredSize
,
NULL
)))
{
mal_device_uninit__dsound
(
pDevice
);
return
MAL_NO_BACKEND
;
}
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.
// Meaning of dwFlags (from MSDN):
//
// DSBCAPS_CTRLPOSITIONNOTIFY
// The buffer has position notification capability.
//
// DSBCAPS_GLOBALFOCUS
// With this flag set, an application using DirectSound can continue to play its buffers if the user switches focus to
// another application, even if the new application uses DirectSound.
//
// DSBCAPS_GETCURRENTPOSITION2
// In the first version of DirectSound, the play cursor was significantly ahead of the actual playing sound on emulated
// sound cards; it was directly behind the write cursor. Now, if the DSBCAPS_GETCURRENTPOSITION2 flag is specified, the
// application can get a more accurate play cursor.
DSBUFFERDESC
descDS
;
memset
(
&
descDS
,
0
,
sizeof
(
DSBUFFERDESC
));
descDS
.
dwSize
=
sizeof
(
DSBUFFERDESC
);
descDS
.
dwFlags
=
DSBCAPS_CTRLPOSITIONNOTIFY
|
DSBCAPS_GLOBALFOCUS
|
DSBCAPS_GETCURRENTPOSITION2
;
descDS
.
dwBufferBytes
=
fragmentSizeInBytes
*
pDevice
->
fragmentCount
;
descDS
.
lpwfxFormat
=
(
WAVEFORMATEX
*
)
&
wf
;
if
(
FAILED
(
IDirectSound_CreateSoundBuffer
((
LPDIRECTSOUND8
)
pDevice
->
dsound
.
pPlayback
,
&
descDS
,
(
LPDIRECTSOUNDBUFFER
*
)
&
pDevice
->
dsound
.
pPlaybackBuffer
,
NULL
)))
{
mal_device_uninit__dsound
(
pDevice
);
return
MAL_NO_BACKEND
;
}
// Notifications are set up via a DIRECTSOUNDNOTIFY object which is retrieved from the buffer.
if
(
FAILED
(
IDirectSoundBuffer8_QueryInterface
((
LPDIRECTSOUNDBUFFER
)
pDevice
->
dsound
.
pPlaybackBuffer
,
g_mal_GUID_IID_DirectSoundNotify
,
(
void
**
)
&
pDevice
->
dsound
.
pNotify
)))
{
mal_device_uninit__dsound
(
pDevice
);
return
MAL_NO_BACKEND
;
}
}
else
{
mal_DirectSoundCaptureCreate8Proc
pDirectSoundCaptureCreate8
=
(
mal_DirectSoundCaptureCreate8Proc
)
GetProcAddress
(
pDevice
->
dsound
.
hDSoundDLL
,
"DirectSoundCaptureCreate8"
);
if
(
pDirectSoundCaptureCreate8
==
NULL
)
{
mal_device_uninit__dsound
(
pDevice
);
return
MAL_NO_BACKEND
;
}
if
(
FAILED
(
pDirectSoundCaptureCreate8
((
pDeviceID
==
NULL
)
?
NULL
:
(
LPCGUID
)
pDeviceID
->
guid
,
(
LPDIRECTSOUNDCAPTURE8
*
)
&
pDevice
->
dsound
.
pCapture
,
NULL
)))
{
mal_device_uninit__dsound
(
pDevice
);
return
MAL_NO_BACKEND
;
}
DSCBUFFERDESC
descDS
;
mal_zero_object
(
&
descDS
);
descDS
.
dwSize
=
sizeof
(
descDS
);
descDS
.
dwFlags
=
0
;
descDS
.
dwBufferBytes
=
fragmentSizeInBytes
*
pDevice
->
fragmentCount
;
descDS
.
lpwfxFormat
=
(
WAVEFORMATEX
*
)
&
wf
;
LPDIRECTSOUNDCAPTUREBUFFER
pDSCB_Temp
;
if
(
FAILED
(
IDirectSoundCapture_CreateCaptureBuffer
((
LPDIRECTSOUNDCAPTURE8
)
pDevice
->
dsound
.
pCapture
,
&
descDS
,
&
pDSCB_Temp
,
NULL
)))
{
mal_device_uninit__dsound
(
pDevice
);
return
MAL_NO_BACKEND
;
}
HRESULT
hr
=
IDirectSoundCapture_QueryInterface
(
pDSCB_Temp
,
g_mal_GUID_IID_IDirectSoundCaptureBuffer8
,
(
LPVOID
*
)
&
pDevice
->
dsound
.
pCaptureBuffer
);
IDirectSoundCaptureBuffer_Release
(
pDSCB_Temp
);
if
(
FAILED
(
hr
))
{
mal_device_uninit__dsound
(
pDevice
);
return
MAL_NO_BACKEND
;
}
// Notifications are set up via a DIRECTSOUNDNOTIFY object which is retrieved from the buffer.
if
(
FAILED
(
IDirectSoundCaptureBuffer8_QueryInterface
((
LPDIRECTSOUNDCAPTUREBUFFER
)
pDevice
->
dsound
.
pCaptureBuffer
,
g_mal_GUID_IID_DirectSoundNotify
,
(
void
**
)
&
pDevice
->
dsound
.
pNotify
)))
{
mal_device_uninit__dsound
(
pDevice
);
return
MAL_NO_BACKEND
;
}
}
// 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
)
{
pDevice
->
dsound
.
pNotifyEvents
[
i
]
=
CreateEventA
(
NULL
,
FALSE
,
FALSE
,
NULL
);
if
(
pDevice
->
dsound
.
pNotifyEvents
[
i
]
==
NULL
)
{
mal_device_uninit__dsound
(
pDevice
);
return
MAL_NO_BACKEND
;
}
// The notification offset is in bytes.
DWORD
dwOffset
;
if
(
type
==
mal_device_type_playback
)
{
dwOffset
=
i
*
fragmentSizeInBytes
;
}
else
{
dwOffset
=
((
i
+
1
)
*
fragmentSizeInBytes
)
%
(
fragmentSizeInBytes
*
pDevice
->
fragmentCount
);
}
notifyPoints
[
i
].
dwOffset
=
dwOffset
;
notifyPoints
[
i
].
hEventNotify
=
pDevice
->
dsound
.
pNotifyEvents
[
i
];
}
if
(
FAILED
(
IDirectSoundNotify_SetNotificationPositions
((
LPDIRECTSOUNDNOTIFY
)
pDevice
->
dsound
.
pNotify
,
pDevice
->
fragmentCount
,
notifyPoints
)))
{
mal_device_uninit__dsound
(
pDevice
);
return
MAL_NO_BACKEND
;
}
// When the device is playing the worker thread will be waiting on a bunch of notification events. To return from
// this wait state we need to signal a special event.
pDevice
->
dsound
.
hStopEvent
=
CreateEventA
(
NULL
,
FALSE
,
FALSE
,
NULL
);
if
(
pDevice
->
dsound
.
hStopEvent
==
NULL
)
{
mal_device_uninit__dsound
(
pDevice
);
return
MAL_FAILED_TO_INIT_BACKEND
;
}
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
;
}
mal_result
mal_device_start__dsound
(
mal_device
*
pDevice
)
{
mal_assert
(
pDevice
!=
NULL
);
// We don't actually start the device here. We instead signal the semaphore on the worker thread and
// let that thread play the device.
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.
ResetEvent
(
pDevice
->
dsound
.
hStopEvent
);
return
MAL_SUCCESS
;
}
mal_result
mal_device_stop__dsound
(
mal_device
*
pDevice
)
{
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
SetEvent
(
pDevice
->
dsound
.
hStopEvent
);
return
MAL_SUCCESS
;
}
#endif
///////////////////////////////////////////////////////////////////////////////
//
// ALSA Backend
//
///////////////////////////////////////////////////////////////////////////////
#ifdef MAL_ENABLE_ALSA
#include <alsa/asoundlib.h>
#define MAL_FLAG_ALSA_USING_MMAP (1 << 15)
#define MAL_FLAG_ALSA_HAS_SEMAPHORE (1 << 16) // Used for cleanup.
#define MAL_FLAG_ALSA_HAS_THREAD (1 << 17) // Used for cleanup.
mal_bool32
mal_device_write__alsa
(
mal_device
*
pDevice
)
{
mal_assert
(
pDevice
!=
NULL
);
if
(
!
mal_device_is_started
(
pDevice
))
{
return
MAL_FALSE
;
}
void
*
pBuffer
=
NULL
;
if
(
pDevice
->
alsa
.
pIntermediaryBuffer
==
NULL
)
{
// mmap.
return
MAL_FALSE
;
}
else
{
// readi/writei.
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
)
{
// mmap.
}
else
{
// readi/writei.
for
(;;)
{
snd_pcm_sframes_t
framesWritten
=
snd_pcm_writei
(
pDevice
->
alsa
.
pPCM
,
pDevice
->
alsa
.
pIntermediaryBuffer
,
pDevice
->
fragmentSizeInFrames
);
if
(
framesWritten
<
0
)
{
if
(
framesWritten
==
-
EAGAIN
)
{
continue
;
// Just keep trying...
}
else
if
(
framesWritten
==
-
EPIPE
)
{
// Underrun. Just recover and try writing again.
if
(
snd_pcm_recover
(
pDevice
->
alsa
.
pPCM
,
framesWritten
,
MAL_TRUE
)
<
0
)
{
return
MAL_FALSE
;
}
framesWritten
=
snd_pcm_writei
(
pDevice
->
alsa
.
pPCM
,
pDevice
->
alsa
.
pIntermediaryBuffer
,
pDevice
->
fragmentSizeInFrames
);
if
(
framesWritten
<
0
)
{
return
MAL_FALSE
;
}
break
;
// Success.
}
else
{
return
MAL_FALSE
;
}
}
else
{
break
;
// Success.
}
}
}
return
MAL_TRUE
;
}
mal_bool32
mal_device_read__alsa
(
mal_device
*
pDevice
)
{
mal_assert
(
pDevice
!=
NULL
);
if
(
!
mal_device_is_started
(
pDevice
))
{
return
MAL_FALSE
;
}
mal_uint32
samplesToSend
=
0
;
void
*
pBuffer
=
NULL
;
if
(
pDevice
->
alsa
.
pIntermediaryBuffer
==
NULL
)
{
// mmap.
return
MAL_FALSE
;
}
else
{
// readi/writei.
snd_pcm_sframes_t
framesRead
=
0
;
for
(;;)
{
framesRead
=
snd_pcm_readi
(
pDevice
->
alsa
.
pPCM
,
pDevice
->
alsa
.
pIntermediaryBuffer
,
pDevice
->
fragmentSizeInFrames
);
if
(
framesRead
<
0
)
{
if
(
framesRead
==
-
EAGAIN
)
{
continue
;
// Just keep trying...
}
else
if
(
framesRead
==
-
EPIPE
)
{
// Overrun. Just recover and try reading again.
if
(
snd_pcm_recover
(
pDevice
->
alsa
.
pPCM
,
framesRead
,
MAL_TRUE
)
<
0
)
{
return
MAL_FALSE
;
}
snd_pcm_sframes_t
framesRead
=
snd_pcm_readi
(
pDevice
->
alsa
.
pPCM
,
pDevice
->
alsa
.
pIntermediaryBuffer
,
pDevice
->
fragmentSizeInFrames
);
if
(
framesRead
<
0
)
{
return
MAL_FALSE
;
}
break
;
// Success.
}
else
{
return
MAL_FALSE
;
}
}
else
{
break
;
// Success.
}
}
samplesToSend
=
framesRead
*
pDevice
->
channels
;
pBuffer
=
pDevice
->
alsa
.
pIntermediaryBuffer
;
}
if
(
pDevice
->
onRecv
&&
samplesToSend
>
0
)
{
pDevice
->
onRecv
(
pDevice
,
samplesToSend
,
pBuffer
);
}
if
(
pDevice
->
alsa
.
pIntermediaryBuffer
==
NULL
)
{
// mmap.
}
else
{
// readi/writei.
}
return
MAL_TRUE
;
}
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_uint32
infoSize
=
*
pCount
;
*
pCount
=
0
;
char
**
ppDeviceHints
;
if
(
snd_device_name_hint
(
-
1
,
"pcm"
,
(
void
***
)
&
ppDeviceHints
)
<
0
)
{
return
MAL_UNKNOWN_ERROR
;
}
char
**
ppNextDeviceHint
=
ppDeviceHints
;
while
(
*
ppNextDeviceHint
!=
NULL
)
{
char
*
NAME
=
snd_device_name_get_hint
(
*
ppNextDeviceHint
,
"NAME"
);
char
*
DESC
=
snd_device_name_get_hint
(
*
ppNextDeviceHint
,
"DESC"
);
char
*
IOID
=
snd_device_name_get_hint
(
*
ppNextDeviceHint
,
"IOID"
);
if
(
IOID
==
NULL
||
(
type
==
mal_device_type_playback
&&
strcmp
(
IOID
,
"Output"
)
==
0
)
||
(
type
==
mal_device_type_capture
&&
strcmp
(
IOID
,
"Input"
)
==
0
))
{
if
(
pInfo
!=
NULL
)
{
if
(
infoSize
>
0
)
{
mal_strncpy_s
(
pInfo
->
id
.
name
,
sizeof
(
pInfo
->
id
.
name
),
NAME
?
NAME
:
""
,
(
size_t
)
-
1
);
mal_strncpy_s
(
pInfo
->
description
,
sizeof
(
pInfo
->
description
),
DESC
?
DESC
:
""
,
(
size_t
)
-
1
);
pInfo
+=
1
;
infoSize
-=
1
;
*
pCount
+=
1
;
}
}
else
{
*
pCount
+=
1
;
}
}
free
(
NAME
);
free
(
DESC
);
free
(
IOID
);
ppNextDeviceHint
+=
1
;
}
snd_device_name_free_hint
((
void
**
)
ppDeviceHints
);
return
MAL_SUCCESS
;
}
void
mal_device_uninit__alsa
(
mal_device
*
pDevice
)
{
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
)
{
snd_pcm_close
((
snd_pcm_t
*
)
pDevice
->
alsa
.
pPCM
);
if
(
pDevice
->
alsa
.
pIntermediaryBuffer
==
NULL
)
{
mal_free
(
pDevice
->
alsa
.
pIntermediaryBuffer
);
}
}
}
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
)
{
mal_assert
(
pDevice
!=
NULL
);
pDevice
->
api
=
mal_api_alsa
;
char
deviceName
[
256
];
if
(
pDeviceID
==
NULL
)
{
strcpy
(
deviceName
,
"default"
);
}
else
{
strcpy
(
deviceName
,
pDeviceID
->
name
);
}
snd_pcm_format_t
formatALSA
;
switch
(
format
)
{
case
mal_format_u8
:
formatALSA
=
SND_PCM_FORMAT_U8
;
break
;
case
mal_format_s16
:
formatALSA
=
SND_PCM_FORMAT_S16_LE
;
break
;
case
mal_format_s32
:
formatALSA
=
SND_PCM_FORMAT_S32_LE
;
break
;
case
mal_format_f32
:
formatALSA
=
SND_PCM_FORMAT_FLOAT_LE
;
break
;
case
mal_format_alaw
:
formatALSA
=
SND_PCM_FORMAT_A_LAW
;
break
;
case
mal_format_mulaw
:
formatALSA
=
SND_PCM_FORMAT_MU_LAW
;
break
;
default:
return
MAL_FORMAT_NOT_SUPPORTED
;
}
if
(
snd_pcm_open
((
snd_pcm_t
**
)
&
pDevice
->
alsa
.
pPCM
,
deviceName
,
(
type
==
mal_device_type_playback
)
?
SND_PCM_STREAM_PLAYBACK
:
SND_PCM_STREAM_CAPTURE
,
0
)
<
0
)
{
mal_device_uninit__alsa
(
pDevice
);
return
MAL_FAILED_TO_INIT_BACKEND
;
}
snd_pcm_hw_params_t
*
pHWParams
=
NULL
;
if
(
snd_pcm_hw_params_malloc
(
&
pHWParams
)
<
0
)
{
mal_device_uninit__alsa
(
pDevice
);
return
MAL_OUT_OF_MEMORY
;
}
if
(
snd_pcm_hw_params_any
((
snd_pcm_t
*
)
pDevice
->
alsa
.
pPCM
,
pHWParams
)
<
0
)
{
snd_pcm_hw_params_free
(
pHWParams
);
mal_device_uninit__alsa
(
pDevice
);
return
MAL_FAILED_TO_INIT_BACKEND
;
}
if
(
snd_pcm_hw_params_set_access
((
snd_pcm_t
*
)
pDevice
->
alsa
.
pPCM
,
pHWParams
,
SND_PCM_ACCESS_RW_INTERLEAVED
)
<
0
)
{
snd_pcm_hw_params_free
(
pHWParams
);
mal_device_uninit__alsa
(
pDevice
);
return
MAL_FAILED_TO_INIT_BACKEND
;
}
if
(
snd_pcm_hw_params_set_format
((
snd_pcm_t
*
)
pDevice
->
alsa
.
pPCM
,
pHWParams
,
formatALSA
)
<
0
)
{
snd_pcm_hw_params_free
(
pHWParams
);
mal_device_uninit__alsa
(
pDevice
);
return
MAL_FAILED_TO_INIT_BACKEND
;
}
if
(
snd_pcm_hw_params_set_rate_near
((
snd_pcm_t
*
)
pDevice
->
alsa
.
pPCM
,
pHWParams
,
&
sampleRate
,
0
)
<
0
)
{
snd_pcm_hw_params_free
(
pHWParams
);
mal_device_uninit__alsa
(
pDevice
);
return
MAL_FAILED_TO_INIT_BACKEND
;
}
if
(
snd_pcm_hw_params_set_channels_near
((
snd_pcm_t
*
)
pDevice
->
alsa
.
pPCM
,
pHWParams
,
&
channels
)
<
0
)
{
snd_pcm_hw_params_free
(
pHWParams
);
mal_device_uninit__alsa
(
pDevice
);
return
MAL_FAILED_TO_INIT_BACKEND
;
}
int
dir
=
0
;
if
(
snd_pcm_hw_params_set_periods_near
((
snd_pcm_t
*
)
pDevice
->
alsa
.
pPCM
,
pHWParams
,
&
fragmentCount
,
&
dir
)
<
0
)
{
snd_pcm_hw_params_free
(
pHWParams
);
mal_device_uninit__alsa
(
pDevice
);
return
MAL_FAILED_TO_INIT_BACKEND
;
}
// A few properties may have been adjusted so we need to make sure the device object is aware.
pDevice
->
channels
=
channels
;
pDevice
->
sampleRate
=
sampleRate
;
pDevice
->
fragmentCount
=
fragmentCount
;
// According to the ALSA documentation, the value passed to snd_pcm_sw_params_set_avail_min() must be a power
// of 2 on some hardware. The value passed to this function is the size in frames of a fragment. Thus, to be
// as robust as possible the size of the hardware buffer should be sized based on the size of the next power-
// of-two frame count.
pDevice
->
fragmentSizeInFrames
=
mal_next_power_of_2
(
fragmentSizeInFrames
);
if
(
snd_pcm_hw_params_set_buffer_size
((
snd_pcm_t
*
)
pDevice
->
alsa
.
pPCM
,
pHWParams
,
pDevice
->
fragmentSizeInFrames
*
pDevice
->
fragmentCount
)
<
0
)
{
snd_pcm_hw_params_free
(
pHWParams
);
mal_device_uninit__alsa
(
pDevice
);
return
MAL_FAILED_TO_INIT_BACKEND
;
}
if
(
snd_pcm_hw_params
((
snd_pcm_t
*
)
pDevice
->
alsa
.
pPCM
,
pHWParams
)
<
0
)
{
snd_pcm_hw_params_free
(
pHWParams
);
mal_device_uninit__alsa
(
pDevice
);
return
MAL_FAILED_TO_INIT_BACKEND
;
}
snd_pcm_hw_params_free
(
pHWParams
);
snd_pcm_sw_params_t
*
pSWParams
=
NULL
;
if
(
snd_pcm_sw_params_malloc
(
&
pSWParams
)
<
0
)
{
mal_device_uninit__alsa
(
pDevice
);
return
MAL_FAILED_TO_INIT_BACKEND
;
}
if
(
snd_pcm_sw_params_current
((
snd_pcm_t
*
)
pDevice
->
alsa
.
pPCM
,
pSWParams
)
!=
0
)
{
snd_pcm_sw_params_free
(
pSWParams
);
mal_device_uninit__alsa
(
pDevice
);
return
MAL_FAILED_TO_INIT_BACKEND
;
}
if
(
snd_pcm_sw_params_set_avail_min
((
snd_pcm_t
*
)
pDevice
->
alsa
.
pPCM
,
pSWParams
,
pDevice
->
fragmentSizeInFrames
)
!=
0
)
{
snd_pcm_sw_params_free
(
pSWParams
);
mal_device_uninit__alsa
(
pDevice
);
return
MAL_FAILED_TO_INIT_BACKEND
;
}
if
(
type
==
mal_device_type_playback
)
{
if
(
snd_pcm_sw_params_set_start_threshold
((
snd_pcm_t
*
)
pDevice
->
alsa
.
pPCM
,
pSWParams
,
pDevice
->
fragmentSizeInFrames
)
!=
0
)
{
snd_pcm_sw_params_free
(
pSWParams
);
mal_device_uninit__alsa
(
pDevice
);
return
MAL_FAILED_TO_INIT_BACKEND
;
}
}
if
(
snd_pcm_sw_params
((
snd_pcm_t
*
)
pDevice
->
alsa
.
pPCM
,
pSWParams
)
!=
0
)
{
snd_pcm_sw_params_free
(
pSWParams
);
mal_device_uninit__alsa
(
pDevice
);
return
MAL_FAILED_TO_INIT_BACKEND
;
}
snd_pcm_sw_params_free
(
pSWParams
);
// If we're _not_ using mmap we need to use an intermediary buffer.
if
(
!
(
pDevice
->
flags
&
MAL_FLAG_ALSA_USING_MMAP
))
{
pDevice
->
alsa
.
pIntermediaryBuffer
=
mal_malloc
(
pDevice
->
fragmentSizeInFrames
*
pDevice
->
channels
*
mal_get_sample_size_in_bytes
(
pDevice
->
format
));
if
(
pDevice
->
alsa
.
pIntermediaryBuffer
==
NULL
)
{
mal_device_uninit__alsa
(
pDevice
);
return
MAL_FAILED_TO_INIT_BACKEND
;
}
}
if
(
!
mal_semaphore_create
(
&
pDevice
->
alsa
.
semaphore
,
0
))
{
mal_device_uninit__alsa
(
pDevice
);
return
MAL_FAILED_TO_INIT_BACKEND
;
}
pDevice
->
flags
|=
MAL_FLAG_ALSA_HAS_SEMAPHORE
;
if
(
!
mal_thread_create
(
&
pDevice
->
alsa
.
thread
,
mal_worker_thread__alsa
,
pDevice
))
{
mal_device_uninit__alsa
(
pDevice
);
return
MAL_FAILED_TO_INIT_BACKEND
;
}
pDevice
->
flags
|=
MAL_FLAG_ALSA_HAS_THREAD
;
return
MAL_SUCCESS
;
}
mal_result
mal_device_start__alsa
(
mal_device
*
pDevice
)
{
mal_assert
(
pDevice
!=
NULL
);
// We don't actually start the device here. We instead signal the semaphore on the worker thread and
// let that thread do the device preparation.
pDevice
->
flags
|=
MAL_FLAG_STARTED
;
mal_semaphore_release
(
&
pDevice
->
alsa
.
semaphore
);
return
MAL_SUCCESS
;
}
mal_result
mal_device_stop__alsa
(
mal_device
*
pDevice
)
{
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
->
alsa
.
semaphore
);
// If we are in snd_pcm_writei()/snd_pcm_readi() we won't return from it until it's signaled. It
// appears from my admittedly limited research and experimentation that we can signal the PCM with
// snd_pcm_prepare(). If don't do this the thread will still return, but it'll wait for the next
// fragment to be processed which might take some time depending on the size of the fragment.
//
// I'm not sure if this is the proper way to do this so best look into this.
snd_pcm_prepare
(
pDevice
->
alsa
.
pPCM
);
return
MAL_SUCCESS
;
}
#endif
// Helper for determining whether or not the given device is initialized.
mal_bool32
mal_device__is_initialized
(
mal_device
*
pDevice
)
{
if
(
pDevice
==
NULL
)
return
MAL_FALSE
;
return
(
pDevice
->
flags
&
MAL_FLAG_INITIALIZED
)
!=
0
;
}
mal_result
mal_enumerate_devices
(
mal_device_type
type
,
mal_uint32
*
pCount
,
mal_device_info
*
pInfo
)
{
if
(
pCount
==
NULL
)
return
MAL_INVALID_ARGS
;
mal_result
result
=
MAL_NO_BACKEND
;
#ifdef MAL_ENABLE_DSOUND
if
(
result
!=
MAL_SUCCESS
)
{
result
=
mal_enumerate_devices__dsound
(
type
,
pCount
,
pInfo
);
}
#endif
#ifdef MAL_ENABLE_ALSA
if
(
result
!=
MAL_SUCCESS
)
{
result
=
mal_enumerate_devices__alsa
(
type
,
pCount
,
pInfo
);
}
#endif
#ifdef MAL_ENABLE_NULL
if
(
result
!=
MAL_SUCCESS
)
{
result
=
mal_enumerate_devices__null
(
type
,
pCount
,
pInfo
);
}
#endif
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
)
{
if
(
pDevice
==
NULL
)
return
MAL_INVALID_ARGS
;
mal_zero_object
(
pDevice
);
if
(
channels
==
0
||
sampleRate
==
0
||
fragmentSizeInFrames
==
0
||
fragmentCount
==
0
)
return
MAL_INVALID_ARGS
;
pDevice
->
type
=
type
;
pDevice
->
format
=
format
;
pDevice
->
channels
=
channels
;
pDevice
->
sampleRate
=
sampleRate
;
pDevice
->
fragmentSizeInFrames
=
fragmentSizeInFrames
;
pDevice
->
fragmentCount
=
fragmentCount
;
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
);
}
#endif
#ifdef MAL_ENABLE_ALSA
if
(
result
!=
MAL_SUCCESS
)
{
result
=
mal_device_init__alsa
(
pDevice
,
type
,
pDeviceID
,
format
,
channels
,
sampleRate
,
fragmentSizeInFrames
,
fragmentCount
);
}
#endif
#ifdef MAL_ENABLE_NULL
if
(
result
!=
MAL_SUCCESS
)
{
result
=
mal_device_init__null
(
pDevice
,
type
,
pDeviceID
,
format
,
channels
,
sampleRate
,
fragmentSizeInFrames
,
fragmentCount
);
}
#endif
if
(
result
!=
MAL_SUCCESS
)
{
return
MAL_NO_BACKEND
;
}
pDevice
->
flags
|=
MAL_FLAG_INITIALIZED
;
return
MAL_SUCCESS
;
}
void
mal_device_uninit
(
mal_device
*
pDevice
)
{
if
(
!
mal_device__is_initialized
(
pDevice
))
return
;
// Make sure the device is stopped first. The backends will probably handle this naturally,
// but I like to do it explicitly for my own sanity.
mal_device_stop
(
pDevice
);
#ifdef MAL_ENABLE_DSOUND
if
(
pDevice
->
api
==
mal_api_dsound
)
{
mal_device_uninit__dsound
(
pDevice
);
}
#endif
#ifdef MAL_ENABLE_ALSA
if
(
pDevice
->
api
==
mal_api_alsa
)
{
mal_device_uninit__alsa
(
pDevice
);
}
#endif
#ifdef MAL_ENABLE_NULL
if
(
pDevice
->
api
==
mal_api_null
)
{
mal_device_uninit__null
(
pDevice
);
}
#endif
mal_zero_object
(
pDevice
);
}
void
mal_device_set_recv_callback
(
mal_device
*
pDevice
,
mal_recv_proc
proc
)
{
if
(
pDevice
==
NULL
)
return
;
pDevice
->
onRecv
=
proc
;
}
void
mal_device_set_send_callback
(
mal_device
*
pDevice
,
mal_send_proc
proc
)
{
if
(
pDevice
==
NULL
)
return
;
pDevice
->
onSend
=
proc
;
}
mal_result
mal_device_start
(
mal_device
*
pDevice
)
{
if
(
pDevice
==
NULL
)
return
MAL_INVALID_ARGS
;
if
(
mal_device_is_started
(
pDevice
))
{
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
if
(
pDevice
->
api
==
mal_api_alsa
)
{
result
=
mal_device_start__alsa
(
pDevice
);
}
#endif
#ifdef MAL_ENABLE_NULL
if
(
pDevice
->
api
==
mal_api_null
)
{
result
=
mal_device_start__null
(
pDevice
);
}
#endif
if
(
result
==
MAL_SUCCESS
)
{
pDevice
->
flags
|=
MAL_FLAG_STARTED
;
}
return
result
;
}
mal_result
mal_device_stop
(
mal_device
*
pDevice
)
{
if
(
pDevice
==
NULL
)
return
MAL_INVALID_ARGS
;
if
(
!
mal_device_is_started
(
pDevice
))
{
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
if
(
pDevice
->
api
==
mal_api_alsa
)
{
result
=
mal_device_stop__alsa
(
pDevice
);
}
#endif
#ifdef MAL_ENABLE_NULL
if
(
pDevice
->
api
==
mal_api_null
)
{
result
=
mal_device_stop__null
(
pDevice
);
}
#endif
if
(
result
==
MAL_SUCCESS
)
{
pDevice
->
flags
&=
~
MAL_FLAG_STARTED
;
}
return
result
;
}
mal_bool32
mal_device_is_started
(
mal_device
*
pDevice
)
{
if
(
pDevice
==
NULL
)
return
MAL_FALSE
;
return
(
pDevice
->
flags
&
MAL_FLAG_STARTED
)
!=
0
;
}
mal_uint32
mal_device_get_fragment_size_in_bytes
(
mal_device
*
pDevice
)
{
if
(
pDevice
==
NULL
)
return
0
;
return
pDevice
->
fragmentSizeInFrames
*
pDevice
->
channels
*
mal_get_sample_size_in_bytes
(
pDevice
->
format
);
}
mal_uint32
mal_get_sample_size_in_bytes
(
mal_format
format
)
{
mal_uint32
sizes
[]
=
{
1
,
// u8
2
,
// s16
3
,
// s24
4
,
// s32
4
,
// f32
8
,
// f64
1
,
// alaw
1
// mulaw
};
return
sizes
[
format
];
}
#endif
// REVISION HISTORY
// ================
//
// v0.1 - TBD
// - Initial versioned release
// 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
// - Logging
// - Implement mmap mode for ALSA.
// - Make device initialization more robust for ALSA
// - Clamp period sizes to their min/max.
// - Support rewinding. This will enable applications to employ better anti-latency.
// DEVELOPMENT NOTES
// =================
//
// ALSA
// ----
// - [DONE] Use snd_pcm_recover() when snd_pcm_writei() or snd_pcm_readi() fails.
/*
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>
*/
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment