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
a18a38f7
Commit
a18a38f7
authored
Oct 19, 2016
by
David Reid
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add initial implementation of the null device.
parent
56b9101c
Changes
1
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
185 additions
and
19 deletions
+185
-19
mini_al.h
mini_al.h
+185
-19
No files found.
mini_al.h
View file @
a18a38f7
...
...
@@ -8,7 +8,7 @@
// mini_al is a small library for making it easy to do audio playback and recording. It's focused on
// simplicity and being light-weight so don't expect all the bells and whistles.
//
// mini_al
's
uses an asynchronous API. Every device is created with it's own thread, with audio data
// mini_al uses an asynchronous API. Every device is created with it's own thread, with audio data
// being either delivered to the application from the device (in the case of recording/capture) or
// delivered from the application to the device in the case of playback. Synchronous APIs are not
// supported in the interest of keeping the library as small and lightweight as possible.
...
...
@@ -16,6 +16,7 @@
// Supported backends:
// - DirectSound (Windows Only)
// - ALSA (Linux Only)
// - null
// - ... and many more in the future.
//
//
...
...
@@ -188,6 +189,12 @@ typedef struct
char
name
[
256
];
}
mal_device_info
;
typedef
struct
{
int64_t
counter
;
}
mal_timer
;
struct
mal_device
{
mal_api
api
;
// DirectSound, ALSA, etc.
...
...
@@ -222,7 +229,6 @@ struct mal_device
/*LPDIRECTSOUNDNOTIFY*/
mal_ptr
pNotify
;
/*HANDLE*/
mal_handle
pNotifyEvents
[
MAL_MAX_FRAGMENTS_DSOUND
];
// One event handle for each fragment.
/*HANDLE*/
mal_handle
hStopEvent
;
mal_uint32
ignoredFragmentCounter
;
// This is used for a cheap hack to skip over some initial notifications when the device is first played.
mal_uint32
lastProcessedFrame
;
// This is circular.
mal_bool32
breakFromMainLoop
;
}
dsound
;
...
...
@@ -241,7 +247,10 @@ struct mal_device
#ifdef MAL_ENABLE_NULL
struct
{
int
unused
;
mal_timer
timer
;
mal_uint32
lastProcessedFrame
;
// This is circular.
mal_bool32
breakFromMainLoop
;
mal_uint8
*
pBuffer
;
// This is malloc()'d and is used as the destination for reading from the client. Typed as mal_uint8 for easy offsetting.
}
null_device
;
#endif
};
...
...
@@ -463,6 +472,7 @@ mal_uint32 mal_get_sample_size_in_bytes(mal_format format);
#ifdef MAL_WIN32
#include <windows.h>
#else
#include <stdlib.h> // For malloc()/free()
#include <string.h> // For memset()
#endif
...
...
@@ -648,6 +658,54 @@ static unsigned int mal_next_power_of_2(unsigned int x)
#endif
///////////////////////////////////////////////////////////////////////////////
//
// Timing
//
///////////////////////////////////////////////////////////////////////////////
#ifdef MAL_WIN32
static
LARGE_INTEGER
g_mal_TimerFrequency
=
{{
0
}};
void
mal_timer_init
(
mal_timer
*
pTimer
)
{
if
(
g_mal_TimerFrequency
.
QuadPart
==
0
)
{
QueryPerformanceFrequency
(
&
g_mal_TimerFrequency
);
}
LARGE_INTEGER
counter
;
QueryPerformanceCounter
(
&
counter
);
pTimer
->
counter
=
(
uint64_t
)
counter
.
QuadPart
;
}
double
mal_timer_get_time_in_seconds
(
mal_timer
*
pTimer
)
{
LARGE_INTEGER
counter
;
if
(
!
QueryPerformanceCounter
(
&
counter
))
{
return
0
;
}
return
(
counter
.
QuadPart
-
pTimer
->
counter
)
/
(
double
)
g_mal_TimerFrequency
.
QuadPart
;
}
#else
void
mal_timer_init
(
mal_timer
*
pTimer
)
{
struct
timespec
newTime
;
clock_gettime
(
CLOCK_PROCESS_CPUTIME_ID
,
&
newTime
);
pTimer
->
counter
=
(
newTime
.
tv_sec
*
1000000000LL
)
+
newTime
.
tv_nsec
;
}
double
mal_timer_get_time_in_seconds
(
mal_timer
*
pTimer
)
{
struct
timespec
newTime
;
clock_gettime
(
CLOCK_PROCESS_CPUTIME_ID
,
&
newTime
);
uint64_t
newTimeCounter
=
(
newTime
.
tv_sec
*
1000000000LL
)
+
newTime
.
tv_nsec
;
uint64_t
oldTimeCounter
=
pTimer
->
counter
;
return
(
newTimeCounter
-
oldTimeCounter
)
/
1000000000
.
0
;
}
#endif
///////////////////////////////////////////////////////////////////////////////
//
...
...
@@ -942,43 +1000,160 @@ static mal_result mal_enumerate_devices__null(mal_device_type type, mal_uint32*
static
void
mal_device_uninit__null
(
mal_device
*
pDevice
)
{
mal_assert
(
pDevice
!=
NULL
);
mal_free
(
pDevice
->
null_device
.
pBuffer
);
}
static
mal_result
mal_device_init__null
(
mal_device
*
pDevice
,
mal_device_type
type
,
mal_device_id
*
pDeviceID
,
mal_format
format
,
mal_uint32
channels
,
mal_uint32
sampleRate
,
mal_uint32
fragmentSizeInFrames
,
mal_uint32
fragmentCount
)
{
mal_assert
(
pDevice
!=
NULL
);
pDevice
->
api
=
mal_api_null
;
mal_timer_init
(
&
pDevice
->
null_device
.
timer
);
// TODO: Implement me.
return
mal_post_error
(
pDevice
,
"Not yet implemented."
,
MAL_ERROR
);
pDevice
->
null_device
.
pBuffer
=
(
mal_uint8
*
)
mal_malloc
(
mal_device_get_fragment_size_in_bytes
(
pDevice
)
*
pDevice
->
fragmentCount
);
if
(
pDevice
->
null_device
.
pBuffer
==
NULL
)
{
return
MAL_OUT_OF_MEMORY
;
}
mal_zero_memory
(
pDevice
->
null_device
.
pBuffer
,
mal_device_get_fragment_size_in_bytes
(
pDevice
)
*
pDevice
->
fragmentCount
);
return
MAL_SUCCESS
;
}
static
mal_result
mal_device__start_backend__null
(
mal_device
*
pDevice
)
{
mal_assert
(
pDevice
!=
NULL
);
return
mal_post_error
(
pDevice
,
"Not yet implemented."
,
MAL_ERROR
);
mal_timer_init
(
&
pDevice
->
null_device
.
timer
);
pDevice
->
null_device
.
lastProcessedFrame
=
0
;
return
MAL_SUCCESS
;
}
static
mal_result
mal_device__stop_backend__null
(
mal_device
*
pDevice
)
{
mal_assert
(
pDevice
!=
NULL
);
return
mal_post_error
(
pDevice
,
"Not yet implemented."
,
MAL_ERROR
)
;
return
MAL_SUCCESS
;
}
static
mal_result
mal_device__break_main_loop__null
(
mal_device
*
pDevice
)
{
mal_assert
(
pDevice
!=
NULL
);
return
mal_post_error
(
pDevice
,
"Not yet implemented."
,
MAL_ERROR
);
pDevice
->
null_device
.
breakFromMainLoop
=
MAL_TRUE
;
return
MAL_SUCCESS
;
}
static
mal_bool32
mal_device__get_current_frame__null
(
mal_device
*
pDevice
,
mal_uint32
*
pCurrentPos
)
{
mal_assert
(
pDevice
!=
NULL
);
mal_assert
(
pCurrentPos
!=
NULL
);
*
pCurrentPos
=
0
;
mal_uint64
currentFrameAbs
=
(
mal_uint64
)(
mal_timer_get_time_in_seconds
(
&
pDevice
->
null_device
.
timer
)
*
pDevice
->
sampleRate
)
/
pDevice
->
channels
;
*
pCurrentPos
=
currentFrameAbs
%
(
pDevice
->
fragmentSizeInFrames
*
pDevice
->
fragmentCount
);
return
MAL_TRUE
;
}
static
mal_bool32
mal_device__get_available_frames__null
(
mal_device
*
pDevice
)
{
mal_assert
(
pDevice
!=
NULL
);
mal_uint32
currentFrame
;
if
(
!
mal_device__get_current_frame__null
(
pDevice
,
&
currentFrame
))
{
return
0
;
}
// In a playback device the last processed frame should always be ahead of the current frame. The space between
// the last processed and current frame (moving forward, starting from the last processed frame) is the amount
// of space available to write.
//
// For a recording device it's the other way around - the last processed frame is always _behind_ the current
// frame and the space between is the available space.
mal_uint32
totalFrameCount
=
pDevice
->
fragmentSizeInFrames
*
pDevice
->
fragmentCount
;
if
(
pDevice
->
type
==
mal_device_type_playback
)
{
mal_uint32
committedBeg
=
currentFrame
;
mal_uint32
committedEnd
=
pDevice
->
null_device
.
lastProcessedFrame
;
if
(
committedEnd
<=
committedBeg
)
{
committedEnd
+=
totalFrameCount
;
// Wrap around.
}
mal_uint32
committedSize
=
(
committedEnd
-
committedBeg
);
mal_assert
(
committedSize
<=
totalFrameCount
);
return
totalFrameCount
-
committedSize
;
}
else
{
mal_uint32
validBeg
=
pDevice
->
null_device
.
lastProcessedFrame
;
mal_uint32
validEnd
=
currentFrame
;
if
(
validEnd
<
validBeg
)
{
validEnd
+=
totalFrameCount
;
// Wrap around.
}
mal_uint32
validSize
=
(
validEnd
-
validBeg
);
mal_assert
(
validSize
<=
totalFrameCount
);
return
validSize
;
}
}
static
mal_uint32
mal_device__wait_for_frames__null
(
mal_device
*
pDevice
)
{
mal_assert
(
pDevice
!=
NULL
);
while
(
!
pDevice
->
null_device
.
breakFromMainLoop
)
{
mal_uint32
framesAvailable
=
mal_device__get_available_frames__null
(
pDevice
);
if
(
framesAvailable
==
0
)
{
return
0
;
}
// Never return more frames that will fit in a fragment.
if
(
framesAvailable
>=
pDevice
->
fragmentSizeInFrames
)
{
return
pDevice
->
fragmentSizeInFrames
;
}
mal_sleep
(
16
);
}
// We'll get here if the loop was terminated. Just return whatever's available.
return
mal_device__get_available_frames__null
(
pDevice
);
}
static
mal_result
mal_device__main_loop__null
(
mal_device
*
pDevice
)
{
mal_assert
(
pDevice
!=
NULL
);
return
mal_post_error
(
pDevice
,
"Not yet implemented."
,
MAL_ERROR
);
pDevice
->
null_device
.
breakFromMainLoop
=
MAL_FALSE
;
while
(
!
pDevice
->
null_device
.
breakFromMainLoop
)
{
mal_uint32
framesAvailable
=
mal_device__wait_for_frames__null
(
pDevice
);
if
(
framesAvailable
==
0
)
{
continue
;
}
// If it's a playback device, don't bother grabbing more data if the device is being stopped.
if
(
pDevice
->
null_device
.
breakFromMainLoop
&&
pDevice
->
type
==
mal_device_type_playback
)
{
return
MAL_FALSE
;
}
mal_uint32
sampleCount
=
framesAvailable
*
pDevice
->
channels
;
mal_uint32
lockOffset
=
pDevice
->
null_device
.
lastProcessedFrame
*
pDevice
->
channels
*
mal_get_sample_size_in_bytes
(
pDevice
->
format
);
mal_uint32
lockSize
=
sampleCount
*
mal_get_sample_size_in_bytes
(
pDevice
->
format
);
if
(
pDevice
->
type
==
mal_device_type_playback
)
{
if
(
pDevice
->
null_device
.
breakFromMainLoop
)
{
return
MAL_FALSE
;
}
mal_device__read_samples_from_client
(
pDevice
,
sampleCount
,
pDevice
->
null_device
.
pBuffer
+
lockOffset
);
}
else
{
mal_zero_memory
(
pDevice
->
null_device
.
pBuffer
+
lockOffset
,
lockSize
);
mal_device__send_samples_to_client
(
pDevice
,
sampleCount
,
pDevice
->
null_device
.
pBuffer
+
lockOffset
);
}
pDevice
->
null_device
.
lastProcessedFrame
=
(
pDevice
->
null_device
.
lastProcessedFrame
+
(
sampleCount
/
pDevice
->
channels
))
%
(
pDevice
->
fragmentSizeInFrames
*
pDevice
->
fragmentCount
);
}
return
MAL_SUCCESS
;
}
static
mal_uint32
mal_device_get_available_rewind_amount__null
(
mal_device
*
pDevice
)
...
...
@@ -1575,14 +1750,6 @@ static mal_result mal_device__main_loop__dsound(mal_device* pDevice)
// 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
;
}
pDevice
->
dsound
.
breakFromMainLoop
=
MAL_FALSE
;
while
(
!
pDevice
->
dsound
.
breakFromMainLoop
)
{
mal_uint32
framesAvailable
=
mal_device__wait_for_frames__dsound
(
pDevice
);
...
...
@@ -2750,7 +2917,6 @@ mal_uint32 mal_get_sample_size_in_bytes(mal_format format)
// TODO
// ====
// - Support rewinding. This will enable applications to employ better anti-latency.
// - Implement the null device.
// - Thread safety for start, stop and rewind.
//
//
...
...
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