Initial Commit
This commit is contained in:
49
thirdparty/miniaudio-0.11.24/extras/osaudio/README.md
vendored
Normal file
49
thirdparty/miniaudio-0.11.24/extras/osaudio/README.md
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
This is just a little experiment to explore some ideas for the kind of API that I would build if I
|
||||
was building my own operation system. The name "osaudio" means Operating System Audio. Or maybe you
|
||||
can think of it as Open Source Audio. It's whatever you want it to be.
|
||||
|
||||
The idea behind this project came about after considering the absurd complexity of audio APIs on
|
||||
various platforms after years of working on miniaudio. This project aims to disprove the idea that
|
||||
complete and flexible audio solutions and simple APIs are mutually exclusive and that it's possible
|
||||
to have both. I challenge anybody to prove me wrong.
|
||||
|
||||
In addition to the above, I also wanted to explore some ideas for a different API design to
|
||||
miniaudio. miniaudio uses a callback model for data transfer, whereas osaudio uses a blocking
|
||||
read/write model.
|
||||
|
||||
This project is essentially just a header file with a reference implementation that uses miniaudio
|
||||
under the hood. You can compile this very easily - just compile osaudio_miniaudio.c, and use
|
||||
osaudio.h just like any other header. There are no dependencies for the header, and the miniaudio
|
||||
implementation obviously requires miniaudio. Adjust the include path in osaudio_miniaudio.c if need
|
||||
be.
|
||||
|
||||
See osaudio.h for full documentation. Below is an example to get you started:
|
||||
|
||||
```c
|
||||
#include "osaudio.h"
|
||||
|
||||
...
|
||||
|
||||
osaudio_t audio;
|
||||
osaudio_config_t config;
|
||||
|
||||
osaudio_config_init(&config, OSAUDIO_OUTPUT);
|
||||
config.format = OSAUDIO_FORMAT_F32;
|
||||
config.channels = 2;
|
||||
config.rate = 48000;
|
||||
|
||||
osaudio_open(&audio, &config);
|
||||
|
||||
osaudio_write(audio, myAudioData, frameCount); // <-- This will block until all of the data has been sent to the device.
|
||||
|
||||
osaudio_close(audio);
|
||||
```
|
||||
|
||||
Compare the code above with the likes of other APIs like Core Audio and PipeWire. I challenge
|
||||
anybody to argue their APIs are cleaner and easier to use than this when it comes to simple audio
|
||||
playback.
|
||||
|
||||
If you have any feedback on this I'd be interested to hear it. In particular, I'd really like to
|
||||
hear from people who believe the likes of Core Audio (Apple), PipeWire, PulseAudio or any other
|
||||
audio API actually have good APIs (they don't!) and what makes their's better and/or worse than
|
||||
this project.
|
||||
604
thirdparty/miniaudio-0.11.24/extras/osaudio/osaudio.h
vendored
Normal file
604
thirdparty/miniaudio-0.11.24/extras/osaudio/osaudio.h
vendored
Normal file
@@ -0,0 +1,604 @@
|
||||
/*
|
||||
This is a simple API for low-level audio playback and capture. A reference implementation using
|
||||
miniaudio is provided in osaudio.c which can be found alongside this file. Consider all code
|
||||
public domain.
|
||||
|
||||
The idea behind this project came about after considering the absurd complexity of audio APIs on
|
||||
various platforms after years of working on miniaudio. This project aims to disprove the idea that
|
||||
complete and flexible audio solutions and simple APIs are mutually exclusive and that it's possible
|
||||
to have both. The idea of reliability through simplicity is the first and foremost goal of this
|
||||
project. The difference between this project and miniaudio is that this project is designed around
|
||||
the idea of what I would build if I was building an audio API for an operating system, such as at
|
||||
the level of WASAPI or ALSA. A cross-platform and cross-backend library like miniaudio is
|
||||
necessarily different in design, but there are indeed things that I would have done differently if
|
||||
given my time again, some of those ideas of which I'm expressing in this project.
|
||||
|
||||
---
|
||||
|
||||
The concept of low-level audio is simple - you have a device, such as a speaker system or a
|
||||
micrphone system, and then you write or read audio data to/from it. So in the case of playback, you
|
||||
need only write your raw audio data to the device which then emits it from the speakers when it's
|
||||
ready. Likewise, for capture you simply read audio data from the device which is filled with data
|
||||
by the microphone.
|
||||
|
||||
A complete low-level audio solution requires the following:
|
||||
|
||||
1) The ability to enumerate devices that are connected to the system.
|
||||
2) The ability to open and close a connection to a device.
|
||||
3) The ability to start and stop the device.
|
||||
4) The ability to write and read audio data to/from the device.
|
||||
5) The ability to query the device for its data configuration.
|
||||
6) The ability to notify the application when certain events occur, such as the device being
|
||||
stopped, or rerouted.
|
||||
|
||||
The API presented here aims to meet all of the above requirements. It uses a single-threaded
|
||||
blocking read/write model for data delivery instead of a callback model. This makes it a bit more
|
||||
flexible since it gives the application full control over the audio thread. It might also make it
|
||||
more feasible to use this API on single-threaded systems.
|
||||
|
||||
Device enumeration is achieved with a single function: osaudio_enumerate(). This function returns
|
||||
an array of osaudio_info_t structures which contain information about each device. The array is
|
||||
allocated must be freed with free(). Contained within the osaudio_info_t struct is, most
|
||||
importantly, the device ID, which is used to open a connection to the device, and the name of the
|
||||
device which can be used to display to the user. For advanced users, it also includes information
|
||||
about the device's native data configuration.
|
||||
|
||||
Opening and closing a connection to a device is achieved with osaudio_open() and osaudio_close().
|
||||
An important concept is that of the ability to configure the device. This is achieved with the
|
||||
osaudio_config_t structure which is passed to osaudio_open(). In addition to the ID of the device,
|
||||
this structure includes information about the desired format, channel count and sample rate. You
|
||||
can also configure the latency of the device, or the buffer size, which is specified in frames. A
|
||||
flags member is used for specifying additional options, such as whether or not to disable automatic
|
||||
rerouting. Finally a callback can be specified for notifications. When osaudio_open() returns, the
|
||||
config structure will be filled with the device's actual configuration. You can inspect the channel
|
||||
map from this structure to know how to arrange the channels in your audio data.
|
||||
|
||||
This API uses a blocking write/read model for pushing and pulling data to/from the device. This
|
||||
is done with the osaudio_write() and osaudio_read() functions. These functions will block until
|
||||
the requested number of frames have been processed or the device is drained or flushed with
|
||||
osaudio_drain() or osaudio_flush() respectively. It is from these functions that the device is
|
||||
started. As soon as you start writing data with osaudio_write() or reading data with
|
||||
osaudio_read(), the device will start. When the device is drained of flushed with osaudio_drain()
|
||||
or osaudio_flush(), the device will be stopped. osaudio_drain() will block until the device has
|
||||
been drained, whereas osaudio_flush() will stop playback immediately and return. You can also pause
|
||||
and resume the device with osaudio_pause() and osaudio_resume(). Since reading and writing is
|
||||
blocking, it can be useful to know how many frames can be written/read without blocking. This is
|
||||
achieved with osaudio_get_avail().
|
||||
|
||||
Querying the device's configuration is achieved with osaudio_get_info(). This function will return
|
||||
a pointer to an osaudio_info_t structure which contains information about the device, most
|
||||
importantly its name and data configuration. The name is important for displaying on a UI, and
|
||||
the data configuration is important for knowing how to format your audio data. The osaudio_info_t
|
||||
structure will contain an array of osaudio_config_t structures. This will contain one entry, which
|
||||
will contain the exact information that was returned in the config structure that was passed to
|
||||
osaudio_open().
|
||||
|
||||
A common requirement is to open a device that represents the operating system's default device.
|
||||
This is done easily by simply passing in NULL for the device ID. Below is an example for opening a
|
||||
default device:
|
||||
|
||||
int result;
|
||||
osaudio_t audio;
|
||||
osaudio_config_t config;
|
||||
|
||||
osaudio_config_init(&config, OSAUDIO_OUTPUT);
|
||||
config.format = OSAUDIO_FORMAT_F32;
|
||||
config.channels = 2;
|
||||
config.rate = 48000;
|
||||
|
||||
result = osaudio_open(&audio, &config);
|
||||
if (result != OSAUDIO_SUCCESS) {
|
||||
printf("Failed to open device.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
...
|
||||
|
||||
osaudio_close(audio);
|
||||
|
||||
In the above example, the default device is opened for playback (OSAUDIO_OUTPUT). The format is
|
||||
set to 32-bit floating point (OSAUDIO_FORMAT_F32), the channel count is set to stereo (2), and the
|
||||
sample rate is set to 48kHz. The device is then closed when we're done with it.
|
||||
|
||||
If instead we wanted to open a specific device, we can do that by passing in the device ID. Below
|
||||
is an example for how to do this:
|
||||
|
||||
int result;
|
||||
osaudio_t audio;
|
||||
osaudio_config_t config;
|
||||
unsigned int infoCount;
|
||||
osaudio_info_t* info;
|
||||
|
||||
result = osaudio_enumerate(&infoCount, &info);
|
||||
if (result != OSAUDIO_SUCCESS) {
|
||||
printf("Failed to enumerate devices.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// ... Iterate over the `info` array and find the device you want to open. Use the `direction` member to discriminate between input and output ...
|
||||
|
||||
osaudio_config_init(&config, OSAUDIO_OUTPUT);
|
||||
config.id = &info[indexOfYourChosenDevice].id;
|
||||
config.format = OSAUDIO_FORMAT_F32;
|
||||
config.channels = 2;
|
||||
config.rate = 48000;
|
||||
|
||||
osaudio_open(&audio, &config);
|
||||
|
||||
...
|
||||
|
||||
osaudio_close(audio);
|
||||
free(info); // The pointer returned by osaudio_enumerate() must be freed with free().
|
||||
|
||||
The id structure is just a 256 byte array that uniquely identifies the device. Implementations may
|
||||
have different representations for device IDs, and A 256 byte array should accommodates all
|
||||
device ID representations. Implementations are required to zero-fill unused bytes. The osaudio_id_t
|
||||
structure can be copied which makes it suitable for serialization and deserialization in situations
|
||||
where you may want to save the device ID to permanent storage so it can be stored in a config file.
|
||||
|
||||
Implementations need to do their own data conversion between the device's native data configuration
|
||||
and the requested configuration. In this case, when the format, channels and rate are specified in
|
||||
the config, they should be unchanged when osaudio_open() returns. If this is not possible,
|
||||
osaudio_open() will return OSAUDIO_FORMAT_NOT_SUPPORTED. However, there are cases where it's useful
|
||||
for a program to use the device's native configuration instead of some fixed configuration. This is
|
||||
achieved by setting the format, channels and rate to 0. Below is an example:
|
||||
|
||||
int result;
|
||||
osaudio_t audio;
|
||||
osaudio_config_t config;
|
||||
|
||||
osaudio_config_init(&config, OSAUDIO_OUTPUT);
|
||||
|
||||
result = osaudio_open(&audio, &config);
|
||||
if (result != OSAUDIO_SUCCESS) {
|
||||
printf("Failed to open device.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// ... `config` will have been updated by osaudio_open() to contain the *actual* format/channels/rate ...
|
||||
|
||||
osaudio_close(audio);
|
||||
|
||||
In addition to the code above, you can explicitly call `osaudio_get_info()` to retrieve the format
|
||||
configuration. If you need to know the native configuration before opening the device, you can use
|
||||
enumeration. The format, channels and rate will be continued in the first item in the configs array.
|
||||
|
||||
The examples above all use playback, but the same applies for capture. The only difference is that
|
||||
the direction is set to OSAUDIO_INPUT instead of OSAUDIO_OUTPUT.
|
||||
|
||||
To output audio from the speakers you need to call osaudio_write(). Likewise, to capture audio from
|
||||
a microphone you need to call osaudio_read(). These functions will block until the requested number
|
||||
of frames have been written or read. The device will start automatically. Below is an example for
|
||||
writing some data to a device:
|
||||
|
||||
int result = osaudio_write(audio, myAudioData, myAudioDataFrameCount);
|
||||
if (result == OSAUDIO_SUCCESS) {
|
||||
printf("Successfully wrote %d frames of audio data.\n", myAudioDataFrameCount);
|
||||
} else {
|
||||
printf("Failed to write audio data.\n");
|
||||
}
|
||||
|
||||
osaudio_write() and osaudio_read() will return OSAUDIO_SUCCESS if the requested number of frames
|
||||
were written or read. You cannot call osaudio_close() while a write or read operation is in
|
||||
progress.
|
||||
|
||||
If you want to write or read audio data without blocking, you can use osaudio_get_avail() to
|
||||
determine how many frames are available for writing or reading. Below is an example:
|
||||
|
||||
unsigned int framesAvailable = osaudio_get_avail(audio);
|
||||
if (result > 0) {
|
||||
printf("There are %d frames available for writing.\n", framesAvailable);
|
||||
} else {
|
||||
printf("There are no frames available for writing.\n");
|
||||
}
|
||||
|
||||
If you want to abort a blocking write or read, you can use osaudio_flush(). This will result in any
|
||||
pending write or read operation being aborted.
|
||||
|
||||
There are several ways of pausing a device. The first is to just drain or flush the device and
|
||||
simply don't do any more read/write operations. A drain and flush will put the device into a
|
||||
stopped state until the next call to either read or write, depending on the device's direction.
|
||||
If, however, this does not suit your requirements, you can use osaudio_pause() and
|
||||
osaudio_resume(). Take note, however, that these functions will result in osaudio_drain() never
|
||||
returning because it'll result in the device being in a stopped state which in turn results in the
|
||||
buffer never being read and therefore never drained.
|
||||
|
||||
Everything is thread safe with a few minor exceptions which has no practical issues for the client:
|
||||
|
||||
* You cannot call any function while osaudio_open() is still in progress.
|
||||
* You cannot call osaudio_close() while any other function is still in progress.
|
||||
* You can only call osaudio_write() and osaudio_read() from one thread at a time.
|
||||
|
||||
None of these issues should be a problem for the client in practice. You won't have a valid
|
||||
osaudio_t object until osaudio_open() has returned. For osaudio_close(), it makes no sense to
|
||||
destroy the object while it's still in use, and doing so would mean the client is using very poor
|
||||
form. For osaudio_write() and osaudio_read(), you wouldn't ever want to call this simultaneously
|
||||
across multiple threads anyway because otherwise you'd end up with garbage audio.
|
||||
|
||||
The rules above only apply when working with a single osaudio_t object. You can have multiple
|
||||
osaudio_t objects open at the same time, and you can call any function on different osaudio_t
|
||||
objects simultaneously from different threads.
|
||||
|
||||
---
|
||||
|
||||
# Feedback
|
||||
|
||||
I'm looking for feedback on the following:
|
||||
|
||||
* Are the supported formats enough? If not, what other formats are needed, and what is the
|
||||
justification for including it? Just because it's the native format on one particular
|
||||
piece of hardware is not enough. Big-endian and little-endian will never be supported. All
|
||||
formats are native-endian.
|
||||
* Are the available channel positions enough? What other positions are needed?
|
||||
* Just some general criticism would be appreciated.
|
||||
|
||||
*/
|
||||
#ifndef osaudio_h
|
||||
#define osaudio_h
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
Support far pointers on relevant platforms (DOS, in particular). The version of this file
|
||||
distributed with an operating system wouldn't need this because they would just have an
|
||||
OS-specific version of this file, but as a reference it's useful to use far pointers here.
|
||||
*/
|
||||
#if defined(__MSDOS__) || defined(_MSDOS) || defined(__DOS__)
|
||||
#define OSAUDIO_FAR far
|
||||
#else
|
||||
#define OSAUDIO_FAR
|
||||
#endif
|
||||
|
||||
typedef struct _osaudio_t* osaudio_t;
|
||||
typedef struct osaudio_config_t osaudio_config_t;
|
||||
typedef struct osaudio_id_t osaudio_id_t;
|
||||
typedef struct osaudio_info_t osaudio_info_t;
|
||||
typedef struct osaudio_notification_t osaudio_notification_t;
|
||||
|
||||
/* Results codes. */
|
||||
typedef int osaudio_result_t;
|
||||
#define OSAUDIO_SUCCESS 0
|
||||
#define OSAUDIO_ERROR -1
|
||||
#define OSAUDIO_INVALID_ARGS -2
|
||||
#define OSAUDIO_INVALID_OPERATION -3
|
||||
#define OSAUDIO_OUT_OF_MEMORY -4
|
||||
#define OSAUDIO_FORMAT_NOT_SUPPORTED -101 /* The requested format is not supported. */
|
||||
#define OSAUDIO_XRUN -102 /* An underrun or overrun occurred. Can be returned by osaudio_read() or osaudio_write(). */
|
||||
#define OSAUDIO_DEVICE_STOPPED -103 /* The device is stopped. Can be returned by osaudio_drain(). It is invalid to call osaudio_drain() on a device that is not running because otherwise it'll get stuck. */
|
||||
|
||||
/* Directions. Cannot be combined. Use separate osaudio_t objects for bidirectional setups. */
|
||||
typedef int osaudio_direction_t;
|
||||
#define OSAUDIO_INPUT 1
|
||||
#define OSAUDIO_OUTPUT 2
|
||||
|
||||
/* All formats are native endian and interleaved. */
|
||||
typedef int osaudio_format_t;
|
||||
#define OSAUDIO_FORMAT_UNKNOWN 0
|
||||
#define OSAUDIO_FORMAT_F32 1
|
||||
#define OSAUDIO_FORMAT_U8 2
|
||||
#define OSAUDIO_FORMAT_S16 3
|
||||
#define OSAUDIO_FORMAT_S24 4 /* Tightly packed. */
|
||||
#define OSAUDIO_FORMAT_S32 5
|
||||
|
||||
/* Channel positions. */
|
||||
typedef unsigned char osaudio_channel_t;
|
||||
#define OSAUDIO_CHANNEL_NONE 0
|
||||
#define OSAUDIO_CHANNEL_MONO 1
|
||||
#define OSAUDIO_CHANNEL_FL 2
|
||||
#define OSAUDIO_CHANNEL_FR 3
|
||||
#define OSAUDIO_CHANNEL_FC 4
|
||||
#define OSAUDIO_CHANNEL_LFE 5
|
||||
#define OSAUDIO_CHANNEL_BL 6
|
||||
#define OSAUDIO_CHANNEL_BR 7
|
||||
#define OSAUDIO_CHANNEL_FLC 8
|
||||
#define OSAUDIO_CHANNEL_FRC 9
|
||||
#define OSAUDIO_CHANNEL_BC 10
|
||||
#define OSAUDIO_CHANNEL_SL 11
|
||||
#define OSAUDIO_CHANNEL_SR 12
|
||||
#define OSAUDIO_CHANNEL_TC 13
|
||||
#define OSAUDIO_CHANNEL_TFL 14
|
||||
#define OSAUDIO_CHANNEL_TFC 15
|
||||
#define OSAUDIO_CHANNEL_TFR 16
|
||||
#define OSAUDIO_CHANNEL_TBL 17
|
||||
#define OSAUDIO_CHANNEL_TBC 18
|
||||
#define OSAUDIO_CHANNEL_TBR 19
|
||||
#define OSAUDIO_CHANNEL_AUX0 20
|
||||
#define OSAUDIO_CHANNEL_AUX1 21
|
||||
#define OSAUDIO_CHANNEL_AUX2 22
|
||||
#define OSAUDIO_CHANNEL_AUX3 23
|
||||
#define OSAUDIO_CHANNEL_AUX4 24
|
||||
#define OSAUDIO_CHANNEL_AUX5 25
|
||||
#define OSAUDIO_CHANNEL_AUX6 26
|
||||
#define OSAUDIO_CHANNEL_AUX7 27
|
||||
#define OSAUDIO_CHANNEL_AUX8 28
|
||||
#define OSAUDIO_CHANNEL_AUX9 29
|
||||
#define OSAUDIO_CHANNEL_AUX10 30
|
||||
#define OSAUDIO_CHANNEL_AUX11 31
|
||||
#define OSAUDIO_CHANNEL_AUX12 32
|
||||
#define OSAUDIO_CHANNEL_AUX13 33
|
||||
#define OSAUDIO_CHANNEL_AUX14 34
|
||||
#define OSAUDIO_CHANNEL_AUX15 35
|
||||
#define OSAUDIO_CHANNEL_AUX16 36
|
||||
#define OSAUDIO_CHANNEL_AUX17 37
|
||||
#define OSAUDIO_CHANNEL_AUX18 38
|
||||
#define OSAUDIO_CHANNEL_AUX19 39
|
||||
#define OSAUDIO_CHANNEL_AUX20 40
|
||||
#define OSAUDIO_CHANNEL_AUX21 41
|
||||
#define OSAUDIO_CHANNEL_AUX22 42
|
||||
#define OSAUDIO_CHANNEL_AUX23 43
|
||||
#define OSAUDIO_CHANNEL_AUX24 44
|
||||
#define OSAUDIO_CHANNEL_AUX25 45
|
||||
#define OSAUDIO_CHANNEL_AUX26 46
|
||||
#define OSAUDIO_CHANNEL_AUX27 47
|
||||
#define OSAUDIO_CHANNEL_AUX28 48
|
||||
#define OSAUDIO_CHANNEL_AUX29 49
|
||||
#define OSAUDIO_CHANNEL_AUX30 50
|
||||
#define OSAUDIO_CHANNEL_AUX31 51
|
||||
|
||||
/* The maximum number of channels supported. */
|
||||
#define OSAUDIO_MAX_CHANNELS 64
|
||||
|
||||
/* Notification types. */
|
||||
typedef int osaudio_notification_type_t;
|
||||
#define OSAUDIO_NOTIFICATION_STARTED 0 /* The device was started in response to a call to osaudio_write() or osaudio_read(). */
|
||||
#define OSAUDIO_NOTIFICATION_STOPPED 1 /* The device was stopped in response to a call to osaudio_drain() or osaudio_flush(). */
|
||||
#define OSAUDIO_NOTIFICATION_REROUTED 2 /* The device was rerouted. Not all implementations need to support rerouting. */
|
||||
#define OSAUDIO_NOTIFICATION_INTERRUPTION_BEGIN 3 /* The device was interrupted due to something like a phone call. */
|
||||
#define OSAUDIO_NOTIFICATION_INTERRUPTION_END 4 /* The interruption has been ended. */
|
||||
|
||||
/* Flags. */
|
||||
#define OSAUDIO_FLAG_NO_REROUTING 1 /* When set, will tell the implementation to disable automatic rerouting if possible. This is a hint and may be ignored by the implementation. */
|
||||
#define OSAUDIO_FLAG_REPORT_XRUN 2 /* When set, will tell the implementation to report underruns and overruns via osaudio_write() and osaudio_read() by aborting and returning OSAUDIO_XRUN. */
|
||||
|
||||
struct osaudio_notification_t
|
||||
{
|
||||
osaudio_notification_type_t type; /* OSAUDIO_NOTIFICATION_* */
|
||||
union
|
||||
{
|
||||
struct
|
||||
{
|
||||
int _unused;
|
||||
} started;
|
||||
struct
|
||||
{
|
||||
int _unused;
|
||||
} stopped;
|
||||
struct
|
||||
{
|
||||
int _unused;
|
||||
} rerouted;
|
||||
struct
|
||||
{
|
||||
int _unused;
|
||||
} interruption;
|
||||
} data;
|
||||
};
|
||||
|
||||
struct osaudio_id_t
|
||||
{
|
||||
char data[256];
|
||||
};
|
||||
|
||||
struct osaudio_config_t
|
||||
{
|
||||
osaudio_id_t* device_id; /* Set to NULL to use default device. When non-null, automatic routing will be disabled. */
|
||||
osaudio_direction_t direction; /* OSAUDIO_INPUT or OSAUDIO_OUTPUT. Cannot be combined. Use separate osaudio_t objects for bidirectional setups. */
|
||||
osaudio_format_t format; /* OSAUDIO_FORMAT_* */
|
||||
unsigned int channels; /* Number of channels. */
|
||||
unsigned int rate; /* Sample rate in seconds. */
|
||||
osaudio_channel_t channel_map[OSAUDIO_MAX_CHANNELS]; /* Leave all items set to 0 for defaults. */
|
||||
unsigned int buffer_size; /* In frames. Set to 0 to use the system default. */
|
||||
unsigned int flags; /* A combination of OSAUDIO_FLAG_* */
|
||||
void (* notification)(void* user_data, const osaudio_notification_t* notification); /* Called when some kind of event occurs, such as a device being closed. Never called from the audio thread. */
|
||||
void* user_data; /* Passed to notification(). */
|
||||
};
|
||||
|
||||
struct osaudio_info_t
|
||||
{
|
||||
osaudio_id_t id;
|
||||
char name[256];
|
||||
osaudio_direction_t direction; /* OSAUDIO_INPUT or OSAUDIO_OUTPUT. */
|
||||
unsigned int config_count;
|
||||
osaudio_config_t* configs;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
Enumerates the available devices.
|
||||
|
||||
On output, `count` will contain the number of items in the `info` array. The array must be freed
|
||||
with free() when it's no longer needed.
|
||||
|
||||
Use the `direction` member to discriminate between input and output devices. Below is an example:
|
||||
|
||||
unsigned int count;
|
||||
osaudio_info_t* info;
|
||||
osaudio_enumerate(&count, &info);
|
||||
|
||||
for (int i = 0; i < count; ++i) {
|
||||
if (info[i].direction == OSAUDIO_OUTPUT) {
|
||||
printf("Output device: %s\n", info[i].name);
|
||||
} else {
|
||||
printf("Input device: %s\n", info[i].name);
|
||||
}
|
||||
}
|
||||
|
||||
You can use the `id` member to open a specific device with osaudio_open(). You do not need to do
|
||||
device enumeration if you only want to open the default device.
|
||||
*/
|
||||
osaudio_result_t osaudio_enumerate(unsigned int* count, osaudio_info_t** info);
|
||||
|
||||
/*
|
||||
Initializes a default config.
|
||||
|
||||
The config object will be cleared to zero, with the direction set to `direction`. This will result
|
||||
in a configuration that uses the device's native format, channels and rate.
|
||||
|
||||
osaudio_config_t is a transparent struct. Just set the relevant fields to the desired values after
|
||||
calling this function. Example:
|
||||
|
||||
osaudio_config_t config;
|
||||
osaudio_config_init(&config, OSAUDIO_OUTPUT);
|
||||
config.format = OSAUDIO_FORMAT_F32;
|
||||
config.channels = 2;
|
||||
config.rate = 48000;
|
||||
*/
|
||||
void osaudio_config_init(osaudio_config_t* config, osaudio_direction_t direction);
|
||||
|
||||
/*
|
||||
Opens a connection to a device.
|
||||
|
||||
On input, config must be filled with the desired configuration. On output, it will be filled with
|
||||
the actual configuration.
|
||||
|
||||
Initialize the config with osaudio_config_init() and then fill in the desired configuration. Below
|
||||
is an example:
|
||||
|
||||
osaudio_config_t config;
|
||||
osaudio_config_init(&config, OSAUDIO_OUTPUT);
|
||||
config.format = OSAUDIO_FORMAT_F32;
|
||||
config.channels = 2;
|
||||
config.rate = 48000;
|
||||
|
||||
When the format, channels or rate are left at their default values, or set to 0 (or
|
||||
OSAUDIO_FORMAT_UNKNOWN for format), the native format, channels or rate will use the device's
|
||||
native configuration:
|
||||
|
||||
osaudio_config_t config;
|
||||
osaudio_config_init(&config, OSAUDIO_OUTPUT);
|
||||
config.format = OSAUDIO_FORMAT_UNKNOWN;
|
||||
config.channels = 0;
|
||||
config.rate = 0;
|
||||
|
||||
The code above is equivalent to this:
|
||||
|
||||
osaudio_config_t config;
|
||||
osaudio_config_init(&config, OSAUDIO_OUTPUT);
|
||||
|
||||
On output the config will be filled with the actual configuration. The implementation will perform
|
||||
any necessary data conversion between the requested data configuration and the device's native
|
||||
configuration. If it cannot, the function will return a OSAUDIO_FORMAT_NOT_SUPPORTED error. In this
|
||||
case the caller can decide to reinitialize the device to use its native configuration and do its
|
||||
own data conversion, or abort if it cannot do so. Use the channel map to determine the ordering of
|
||||
your channels. Automatic channel map conversion is not performed - that must be done manually by
|
||||
the caller when transferring data to/from the device.
|
||||
|
||||
Close the device with osaudio_close().
|
||||
|
||||
Returns 0 on success, any other error code on failure.
|
||||
*/
|
||||
osaudio_result_t osaudio_open(osaudio_t* audio, osaudio_config_t* config);
|
||||
|
||||
/*
|
||||
Closes a connection to a device.
|
||||
|
||||
As soon as this function is called, the device should be considered invalid and unusable. Do not
|
||||
attempt to use the audio object once this function has been called.
|
||||
|
||||
It's invalid to call this while any other function is still running. You can use osaudio_flush() to
|
||||
quickly abort any pending writes or reads. You can also use osaudio_drain() to wait for all pending
|
||||
writes or reads to complete.
|
||||
|
||||
Returns 0 on success, < 0 on failure.
|
||||
*/
|
||||
osaudio_result_t osaudio_close(osaudio_t audio);
|
||||
|
||||
/*
|
||||
Writes audio data to the device.
|
||||
|
||||
This will block until all data has been written or the device is closed.
|
||||
|
||||
You can only write from a single thread at any given time. If you want to write from multiple
|
||||
threads, you need to use your own synchronization mechanism.
|
||||
|
||||
This will automatically start the device if frame_count is > 0 and it's not in a paused state.
|
||||
|
||||
Use osaudio_get_avail() to determine how much data can be written without blocking.
|
||||
|
||||
Returns 0 on success, < 0 on failure.
|
||||
*/
|
||||
osaudio_result_t osaudio_write(osaudio_t audio, const void OSAUDIO_FAR* data, unsigned int frame_count);
|
||||
|
||||
/*
|
||||
Reads audio data from the device.
|
||||
|
||||
This will block until the requested number of frames has been read or the device is closed.
|
||||
|
||||
You can only read from a single thread at any given time. If you want to read from multiple
|
||||
threads, you need to use your own synchronization mechanism.
|
||||
|
||||
This will automatically start the device if frame_count is > 0 and it's not in a paused state.
|
||||
|
||||
Use osaudio_get_avail() to determine how much data can be read without blocking.
|
||||
|
||||
Returns 0 on success, < 0 on failure.
|
||||
*/
|
||||
osaudio_result_t osaudio_read(osaudio_t audio, void OSAUDIO_FAR* data, unsigned int frame_count);
|
||||
|
||||
/*
|
||||
Drains the device.
|
||||
|
||||
This will block until all pending reads or writes have completed.
|
||||
|
||||
If after calling this function another call to osaudio_write() or osaudio_read() is made, the
|
||||
device will be resumed like normal.
|
||||
|
||||
It is invalid to call this while the device is paused.
|
||||
|
||||
Returns 0 on success, < 0 on failure.
|
||||
*/
|
||||
osaudio_result_t osaudio_drain(osaudio_t audio);
|
||||
|
||||
/*
|
||||
Flushes the device.
|
||||
|
||||
This will immediately flush any pending reads or writes. It will not block. Any in-progress reads
|
||||
or writes will return immediately.
|
||||
|
||||
If after calling this function another thread starts reading or writing, the device will be resumed
|
||||
like normal.
|
||||
|
||||
Returns 0 on success, < 0 on failure.
|
||||
*/
|
||||
osaudio_result_t osaudio_flush(osaudio_t audio);
|
||||
|
||||
/*
|
||||
Pauses or resumes the device.
|
||||
|
||||
Pausing a device will trigger a OSAUDIO_NOTIFICATION_STOPPED notification. Resuming a device will
|
||||
trigger a OSAUDIO_NOTIFICATION_STARTED notification.
|
||||
|
||||
Returns 0 on success, < 0 on failure.
|
||||
*/
|
||||
osaudio_result_t osaudio_pause(osaudio_t audio);
|
||||
|
||||
/*
|
||||
Resumes the device.
|
||||
|
||||
Returns 0 on success, < 0 on failure.
|
||||
*/
|
||||
osaudio_result_t osaudio_resume(osaudio_t audio);
|
||||
|
||||
/*
|
||||
Returns the number of frames that can be read or written without blocking.
|
||||
*/
|
||||
unsigned int osaudio_get_avail(osaudio_t audio);
|
||||
|
||||
/*
|
||||
Gets information about the device.
|
||||
|
||||
There will be one item in the configs array which will contain the device's current configuration,
|
||||
the contents of which will match that of the config that was returned by osaudio_open().
|
||||
|
||||
Returns NULL on failure. Do not free the returned pointer. It's up to the implementation to manage
|
||||
the memory of this object.
|
||||
*/
|
||||
const osaudio_info_t* osaudio_get_info(osaudio_t audio);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* osaudio_h */
|
||||
1141
thirdparty/miniaudio-0.11.24/extras/osaudio/osaudio_dos_soundblaster.c
vendored
Normal file
1141
thirdparty/miniaudio-0.11.24/extras/osaudio/osaudio_dos_soundblaster.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
948
thirdparty/miniaudio-0.11.24/extras/osaudio/osaudio_miniaudio.c
vendored
Normal file
948
thirdparty/miniaudio-0.11.24/extras/osaudio/osaudio_miniaudio.c
vendored
Normal file
@@ -0,0 +1,948 @@
|
||||
/*
|
||||
Consider this a reference implementation of osaudio. It uses miniaudio under the hood. You can add
|
||||
this file directly to your source tree, but you may need to update the miniaudio path.
|
||||
|
||||
This will use a mutex in osaudio_read() and osaudio_write(). It's a low-contention lock that's only
|
||||
used for the purpose of osaudio_drain(), but it's still a lock nonetheless. I'm not worrying about
|
||||
this too much right now because this is just an example implementation, but I might improve on this
|
||||
at a later date.
|
||||
*/
|
||||
#ifndef osaudio_miniaudio_c
|
||||
#define osaudio_miniaudio_c
|
||||
|
||||
#include "osaudio.h"
|
||||
|
||||
/*
|
||||
If you would rather define your own implementation of miniaudio, define OSAUDIO_NO_MINIAUDIO_IMPLEMENTATION. If you do this,
|
||||
you need to make sure you include the implmeentation before osaudio.c. This would only really be useful if you are wanting
|
||||
to do a unity build which uses other parts of miniaudio that this file is currently excluding.
|
||||
*/
|
||||
#ifndef OSAUDIO_NO_MINIAUDIO_IMPLEMENTATION
|
||||
#define MA_API static
|
||||
#define MA_NO_DECODING
|
||||
#define MA_NO_ENCODING
|
||||
#define MA_NO_RESOURCE_MANAGER
|
||||
#define MA_NO_NODE_GRAPH
|
||||
#define MA_NO_ENGINE
|
||||
#define MA_NO_GENERATION
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "../../miniaudio.h"
|
||||
#endif
|
||||
|
||||
struct _osaudio_t
|
||||
{
|
||||
ma_device device;
|
||||
osaudio_info_t info;
|
||||
osaudio_config_t config; /* info.configs will point to this. */
|
||||
ma_pcm_rb buffer;
|
||||
ma_semaphore bufferSemaphore; /* The semaphore for controlling access to the buffer. The audio thread will release the semaphore. The read and write functions will wait on it. */
|
||||
ma_atomic_bool32 isActive; /* Starts off as false. Set to true when config.buffer_size data has been written in the case of playback, or as soon as osaudio_read() is called in the case of capture. */
|
||||
ma_atomic_bool32 isPaused;
|
||||
ma_atomic_bool32 isFlushed; /* When set, activation of the device will flush any data that's currently in the buffer. Defaults to false, and will be set to true in osaudio_drain() and osaudio_flush(). */
|
||||
ma_atomic_bool32 xrunDetected; /* Used for detecting when an xrun has occurred and returning from osaudio_read/write() when OSAUDIO_FLAG_REPORT_XRUN is enabled. */
|
||||
ma_spinlock activateLock; /* Used for starting and stopping the device. Needed because two variables control this - isActive and isPaused. */
|
||||
ma_mutex drainLock; /* Used for osaudio_drain(). For mutal exclusion between drain() and read()/write(). Technically results in a lock in read()/write(), but not overthinking that since this is just a reference for now. */
|
||||
};
|
||||
|
||||
|
||||
static ma_bool32 osaudio_g_is_backend_known = MA_FALSE;
|
||||
static ma_backend osaudio_g_backend = ma_backend_wasapi;
|
||||
static ma_context osaudio_g_context;
|
||||
static ma_mutex osaudio_g_context_lock; /* Only used for device enumeration. Created and destroyed with our context. */
|
||||
static ma_uint32 osaudio_g_refcount = 0;
|
||||
static ma_spinlock osaudio_g_lock = 0;
|
||||
|
||||
|
||||
static osaudio_result_t osaudio_result_from_miniaudio(ma_result result)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case MA_SUCCESS: return OSAUDIO_SUCCESS;
|
||||
case MA_INVALID_ARGS: return OSAUDIO_INVALID_ARGS;
|
||||
case MA_INVALID_OPERATION: return OSAUDIO_INVALID_OPERATION;
|
||||
case MA_OUT_OF_MEMORY: return OSAUDIO_OUT_OF_MEMORY;
|
||||
default: return OSAUDIO_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
static ma_format osaudio_format_to_miniaudio(osaudio_format_t format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case OSAUDIO_FORMAT_F32: return ma_format_f32;
|
||||
case OSAUDIO_FORMAT_U8: return ma_format_u8;
|
||||
case OSAUDIO_FORMAT_S16: return ma_format_s16;
|
||||
case OSAUDIO_FORMAT_S24: return ma_format_s24;
|
||||
case OSAUDIO_FORMAT_S32: return ma_format_s32;
|
||||
default: return ma_format_unknown;
|
||||
}
|
||||
}
|
||||
|
||||
static osaudio_format_t osaudio_format_from_miniaudio(ma_format format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case ma_format_f32: return OSAUDIO_FORMAT_F32;
|
||||
case ma_format_u8: return OSAUDIO_FORMAT_U8;
|
||||
case ma_format_s16: return OSAUDIO_FORMAT_S16;
|
||||
case ma_format_s24: return OSAUDIO_FORMAT_S24;
|
||||
case ma_format_s32: return OSAUDIO_FORMAT_S32;
|
||||
default: return OSAUDIO_FORMAT_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static osaudio_channel_t osaudio_channel_from_miniaudio(ma_channel channel)
|
||||
{
|
||||
/* Channel positions between here and miniaudio will remain in sync. */
|
||||
return (osaudio_channel_t)channel;
|
||||
}
|
||||
|
||||
static ma_channel osaudio_channel_to_miniaudio(osaudio_channel_t channel)
|
||||
{
|
||||
/* Channel positions between here and miniaudio will remain in sync. */
|
||||
return (ma_channel)channel;
|
||||
}
|
||||
|
||||
|
||||
static void osaudio_dummy_data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
|
||||
{
|
||||
(void)pDevice;
|
||||
(void)pOutput;
|
||||
(void)pInput;
|
||||
(void)frameCount;
|
||||
}
|
||||
|
||||
static osaudio_result_t osaudio_determine_miniaudio_backend(ma_backend* pBackend, ma_device* pDummyDevice)
|
||||
{
|
||||
ma_device dummyDevice;
|
||||
ma_device_config dummyDeviceConfig;
|
||||
ma_result result;
|
||||
|
||||
/*
|
||||
To do this we initialize a dummy device. We allow the caller to make use of this device as an optimization. This is
|
||||
only used by osaudio_enumerate_devices() because that can make use of the context from the dummy device rather than
|
||||
having to create its own. pDummyDevice can be null.
|
||||
*/
|
||||
if (pDummyDevice == NULL) {
|
||||
pDummyDevice = &dummyDevice;
|
||||
}
|
||||
|
||||
dummyDeviceConfig = ma_device_config_init(ma_device_type_playback);
|
||||
dummyDeviceConfig.dataCallback = osaudio_dummy_data_callback;
|
||||
|
||||
result = ma_device_init(NULL, &dummyDeviceConfig, pDummyDevice);
|
||||
if (result != MA_SUCCESS || pDummyDevice->pContext->backend == ma_backend_null) {
|
||||
/* Failed to open a default playback device. Try capture. */
|
||||
if (result == MA_SUCCESS) {
|
||||
/* This means we successfully initialize a device, but its backend is null. It could be that there's no playback devices attached. Try capture. */
|
||||
ma_device_uninit(pDummyDevice);
|
||||
}
|
||||
|
||||
dummyDeviceConfig = ma_device_config_init(ma_device_type_capture);
|
||||
result = ma_device_init(NULL, &dummyDeviceConfig, pDummyDevice);
|
||||
}
|
||||
|
||||
if (result != MA_SUCCESS) {
|
||||
return osaudio_result_from_miniaudio(result);
|
||||
}
|
||||
|
||||
*pBackend = pDummyDevice->pContext->backend;
|
||||
|
||||
/* We're done. */
|
||||
if (pDummyDevice == &dummyDevice) {
|
||||
ma_device_uninit(&dummyDevice);
|
||||
}
|
||||
|
||||
return OSAUDIO_SUCCESS;
|
||||
}
|
||||
|
||||
static osaudio_result_t osaudio_ref_context_nolock()
|
||||
{
|
||||
/* Initialize the global context if necessary. */
|
||||
if (osaudio_g_refcount == 0) {
|
||||
osaudio_result_t result;
|
||||
|
||||
/* If we haven't got a known context, we'll need to determine it here. */
|
||||
if (osaudio_g_is_backend_known == MA_FALSE) {
|
||||
result = osaudio_determine_miniaudio_backend(&osaudio_g_backend, NULL);
|
||||
if (result != OSAUDIO_SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
result = osaudio_result_from_miniaudio(ma_context_init(&osaudio_g_backend, 1, NULL, &osaudio_g_context));
|
||||
if (result != OSAUDIO_SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Need a mutex for device enumeration. */
|
||||
ma_mutex_init(&osaudio_g_context_lock);
|
||||
}
|
||||
|
||||
osaudio_g_refcount += 1;
|
||||
|
||||
return OSAUDIO_SUCCESS;
|
||||
}
|
||||
|
||||
static osaudio_result_t osaudio_unref_context_nolock()
|
||||
{
|
||||
if (osaudio_g_refcount == 0) {
|
||||
return OSAUDIO_INVALID_OPERATION;
|
||||
}
|
||||
|
||||
osaudio_g_refcount -= 1;
|
||||
|
||||
/* Uninitialize the context if we don't have any more references. */
|
||||
if (osaudio_g_refcount == 0) {
|
||||
ma_context_uninit(&osaudio_g_context);
|
||||
ma_mutex_uninit(&osaudio_g_context_lock);
|
||||
}
|
||||
|
||||
return OSAUDIO_SUCCESS;
|
||||
}
|
||||
|
||||
static ma_context* osaudio_ref_context()
|
||||
{
|
||||
osaudio_result_t result;
|
||||
|
||||
ma_spinlock_lock(&osaudio_g_lock);
|
||||
{
|
||||
result = osaudio_ref_context_nolock();
|
||||
}
|
||||
ma_spinlock_unlock(&osaudio_g_lock);
|
||||
|
||||
if (result != OSAUDIO_SUCCESS) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return &osaudio_g_context;
|
||||
}
|
||||
|
||||
static osaudio_result_t osaudio_unref_context()
|
||||
{
|
||||
osaudio_result_t result;
|
||||
|
||||
ma_spinlock_lock(&osaudio_g_lock);
|
||||
{
|
||||
result = osaudio_unref_context_nolock();
|
||||
}
|
||||
ma_spinlock_unlock(&osaudio_g_lock);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
static void osaudio_info_from_miniaudio(osaudio_info_t* info, const ma_device_info* infoMA)
|
||||
{
|
||||
unsigned int iNativeConfig;
|
||||
|
||||
/* It just so happens, by absolutely total coincidence, that the size of the ID and name are the same between here and miniaudio. What are the odds?! */
|
||||
memcpy(info->id.data, &infoMA->id, sizeof(info->id.data));
|
||||
memcpy(info->name, infoMA->name, sizeof(info->name));
|
||||
|
||||
info->config_count = (unsigned int)infoMA->nativeDataFormatCount;
|
||||
for (iNativeConfig = 0; iNativeConfig < info->config_count; iNativeConfig += 1) {
|
||||
unsigned int iChannel;
|
||||
|
||||
info->configs[iNativeConfig].device_id = &info->id;
|
||||
info->configs[iNativeConfig].direction = info->direction;
|
||||
info->configs[iNativeConfig].format = osaudio_format_from_miniaudio(infoMA->nativeDataFormats[iNativeConfig].format);
|
||||
info->configs[iNativeConfig].channels = (unsigned int)infoMA->nativeDataFormats[iNativeConfig].channels;
|
||||
info->configs[iNativeConfig].rate = (unsigned int)infoMA->nativeDataFormats[iNativeConfig].sampleRate;
|
||||
|
||||
/* Apparently miniaudio does not report channel positions. I don't know why I'm not doing that. */
|
||||
for (iChannel = 0; iChannel < info->configs[iNativeConfig].channels; iChannel += 1) {
|
||||
info->configs[iNativeConfig].channel_map[iChannel] = OSAUDIO_CHANNEL_NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static osaudio_result_t osaudio_enumerate_nolock(unsigned int* count, osaudio_info_t** info, ma_context* pContext)
|
||||
{
|
||||
osaudio_result_t result;
|
||||
ma_device_info* pPlaybackInfos;
|
||||
ma_uint32 playbackCount;
|
||||
ma_device_info* pCaptureInfos;
|
||||
ma_uint32 captureCount;
|
||||
ma_uint32 iInfo;
|
||||
size_t allocSize;
|
||||
osaudio_info_t* pRunningInfo;
|
||||
osaudio_config_t* pRunningConfig;
|
||||
|
||||
/* We now need to retrieve the device information from miniaudio. */
|
||||
result = osaudio_result_from_miniaudio(ma_context_get_devices(pContext, &pPlaybackInfos, &playbackCount, &pCaptureInfos, &captureCount));
|
||||
if (result != OSAUDIO_SUCCESS) {
|
||||
osaudio_unref_context();
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
Because the caller needs to free the returned pointer it's important that we keep it all in one allocation. Because there can be
|
||||
a variable number of native configs we'll have to compute the size of the allocation first, and then do a second pass to fill
|
||||
out the data.
|
||||
*/
|
||||
allocSize = ((size_t)playbackCount + (size_t)captureCount) * sizeof(osaudio_info_t);
|
||||
|
||||
/* Now we need to iterate over each playback and capture device and add up the number of native configs. */
|
||||
for (iInfo = 0; iInfo < playbackCount; iInfo += 1) {
|
||||
ma_context_get_device_info(pContext, ma_device_type_playback, &pPlaybackInfos[iInfo].id, &pPlaybackInfos[iInfo]);
|
||||
allocSize += pPlaybackInfos[iInfo].nativeDataFormatCount * sizeof(osaudio_config_t);
|
||||
}
|
||||
for (iInfo = 0; iInfo < captureCount; iInfo += 1) {
|
||||
ma_context_get_device_info(pContext, ma_device_type_capture, &pCaptureInfos[iInfo].id, &pCaptureInfos[iInfo]);
|
||||
allocSize += pCaptureInfos[iInfo].nativeDataFormatCount * sizeof(osaudio_config_t);
|
||||
}
|
||||
|
||||
/* Now that we know the size of the allocation we can allocate it. */
|
||||
*info = (osaudio_info_t*)calloc(1, allocSize);
|
||||
if (*info == NULL) {
|
||||
osaudio_unref_context();
|
||||
return OSAUDIO_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
pRunningInfo = *info;
|
||||
pRunningConfig = (osaudio_config_t*)(((unsigned char*)*info) + (((size_t)playbackCount + (size_t)captureCount) * sizeof(osaudio_info_t)));
|
||||
|
||||
for (iInfo = 0; iInfo < playbackCount; iInfo += 1) {
|
||||
pRunningInfo->direction = OSAUDIO_OUTPUT;
|
||||
pRunningInfo->configs = pRunningConfig;
|
||||
osaudio_info_from_miniaudio(pRunningInfo, &pPlaybackInfos[iInfo]);
|
||||
|
||||
pRunningConfig += pRunningInfo->config_count;
|
||||
pRunningInfo += 1;
|
||||
}
|
||||
|
||||
for (iInfo = 0; iInfo < captureCount; iInfo += 1) {
|
||||
pRunningInfo->direction = OSAUDIO_INPUT;
|
||||
pRunningInfo->configs = pRunningConfig;
|
||||
osaudio_info_from_miniaudio(pRunningInfo, &pPlaybackInfos[iInfo]);
|
||||
|
||||
pRunningConfig += pRunningInfo->config_count;
|
||||
pRunningInfo += 1;
|
||||
}
|
||||
|
||||
*count = (unsigned int)(playbackCount + captureCount);
|
||||
|
||||
return OSAUDIO_SUCCESS;
|
||||
}
|
||||
|
||||
osaudio_result_t osaudio_enumerate(unsigned int* count, osaudio_info_t** info)
|
||||
{
|
||||
osaudio_result_t result;
|
||||
ma_context* pContext = NULL;
|
||||
|
||||
if (count != NULL) {
|
||||
*count = 0;
|
||||
}
|
||||
if (info != NULL) {
|
||||
*info = NULL;
|
||||
}
|
||||
|
||||
if (count == NULL || info == NULL) {
|
||||
return OSAUDIO_INVALID_ARGS;
|
||||
}
|
||||
|
||||
pContext = osaudio_ref_context();
|
||||
if (pContext == NULL) {
|
||||
return OSAUDIO_ERROR;
|
||||
}
|
||||
|
||||
ma_mutex_lock(&osaudio_g_context_lock);
|
||||
{
|
||||
result = osaudio_enumerate_nolock(count, info, pContext);
|
||||
}
|
||||
ma_mutex_unlock(&osaudio_g_context_lock);
|
||||
|
||||
/* We're done. We can now return. */
|
||||
osaudio_unref_context();
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
void osaudio_config_init(osaudio_config_t* config, osaudio_direction_t direction)
|
||||
{
|
||||
if (config == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
memset(config, 0, sizeof(*config));
|
||||
config->direction = direction;
|
||||
}
|
||||
|
||||
|
||||
static void osaudio_data_callback_playback(osaudio_t audio, void* pOutput, ma_uint32 frameCount)
|
||||
{
|
||||
/*
|
||||
If there's content in the buffer, read from it and release the semaphore. There needs to be a whole frameCount chunk
|
||||
in the buffer so we can keep everything in nice clean chunks. When we read from the buffer, we release a semaphore
|
||||
which will allow the main thread to write more data to the buffer.
|
||||
*/
|
||||
ma_uint32 framesToRead;
|
||||
ma_uint32 framesProcessed;
|
||||
void* pBuffer;
|
||||
|
||||
framesToRead = ma_pcm_rb_available_read(&audio->buffer);
|
||||
if (framesToRead > frameCount) {
|
||||
framesToRead = frameCount;
|
||||
}
|
||||
|
||||
framesProcessed = framesToRead;
|
||||
|
||||
/* For robustness we should run this in a loop in case the buffer wraps around. */
|
||||
while (frameCount > 0) {
|
||||
framesToRead = frameCount;
|
||||
|
||||
ma_pcm_rb_acquire_read(&audio->buffer, &framesToRead, &pBuffer);
|
||||
if (framesToRead == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
memcpy(pOutput, pBuffer, framesToRead * ma_get_bytes_per_frame(audio->device.playback.format, audio->device.playback.channels));
|
||||
ma_pcm_rb_commit_read(&audio->buffer, framesToRead);
|
||||
|
||||
frameCount -= framesToRead;
|
||||
pOutput = ((unsigned char*)pOutput) + (framesToRead * ma_get_bytes_per_frame(audio->device.playback.format, audio->device.playback.channels));
|
||||
}
|
||||
|
||||
/* Make sure we release the semaphore if we ended up reading anything. */
|
||||
if (framesProcessed > 0) {
|
||||
ma_semaphore_release(&audio->bufferSemaphore);
|
||||
}
|
||||
|
||||
if (frameCount > 0) {
|
||||
/* Underrun. Pad with silence. */
|
||||
ma_silence_pcm_frames(pOutput, frameCount, audio->device.playback.format, audio->device.playback.channels);
|
||||
ma_atomic_bool32_set(&audio->xrunDetected, MA_TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
static void osaudio_data_callback_capture(osaudio_t audio, const void* pInput, ma_uint32 frameCount)
|
||||
{
|
||||
/* If there's space in the buffer, write to it and release the semaphore. The semaphore is only released on full-chunk boundaries. */
|
||||
ma_uint32 framesToWrite;
|
||||
ma_uint32 framesProcessed;
|
||||
void* pBuffer;
|
||||
|
||||
framesToWrite = ma_pcm_rb_available_write(&audio->buffer);
|
||||
if (framesToWrite > frameCount) {
|
||||
framesToWrite = frameCount;
|
||||
}
|
||||
|
||||
framesProcessed = framesToWrite;
|
||||
|
||||
while (frameCount > 0) {
|
||||
framesToWrite = frameCount;
|
||||
|
||||
ma_pcm_rb_acquire_write(&audio->buffer, &framesToWrite, &pBuffer);
|
||||
if (framesToWrite == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
memcpy(pBuffer, pInput, framesToWrite * ma_get_bytes_per_frame(audio->device.capture.format, audio->device.capture.channels));
|
||||
ma_pcm_rb_commit_write(&audio->buffer, framesToWrite);
|
||||
|
||||
frameCount -= framesToWrite;
|
||||
pInput = ((unsigned char*)pInput) + (framesToWrite * ma_get_bytes_per_frame(audio->device.capture.format, audio->device.capture.channels));
|
||||
}
|
||||
|
||||
/* Make sure we release the semaphore if we ended up reading anything. */
|
||||
if (framesProcessed > 0) {
|
||||
ma_semaphore_release(&audio->bufferSemaphore);
|
||||
}
|
||||
|
||||
if (frameCount > 0) {
|
||||
/* Overrun. Not enough room to move our input data into the buffer. */
|
||||
ma_atomic_bool32_set(&audio->xrunDetected, MA_TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
static void osaudio_nofication_callback(const ma_device_notification* pNotification)
|
||||
{
|
||||
osaudio_t audio = (osaudio_t)pNotification->pDevice->pUserData;
|
||||
|
||||
if (audio->config.notification != NULL) {
|
||||
osaudio_notification_t notification;
|
||||
|
||||
switch (pNotification->type)
|
||||
{
|
||||
case ma_device_notification_type_started:
|
||||
{
|
||||
notification.type = OSAUDIO_NOTIFICATION_STARTED;
|
||||
} break;
|
||||
case ma_device_notification_type_stopped:
|
||||
{
|
||||
notification.type = OSAUDIO_NOTIFICATION_STOPPED;
|
||||
} break;
|
||||
case ma_device_notification_type_rerouted:
|
||||
{
|
||||
notification.type = OSAUDIO_NOTIFICATION_REROUTED;
|
||||
} break;
|
||||
case ma_device_notification_type_interruption_began:
|
||||
{
|
||||
notification.type = OSAUDIO_NOTIFICATION_INTERRUPTION_BEGIN;
|
||||
} break;
|
||||
case ma_device_notification_type_interruption_ended:
|
||||
{
|
||||
notification.type = OSAUDIO_NOTIFICATION_INTERRUPTION_END;
|
||||
} break;
|
||||
}
|
||||
|
||||
audio->config.notification(audio->config.user_data, ¬ification);
|
||||
}
|
||||
}
|
||||
|
||||
static void osaudio_data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
|
||||
{
|
||||
osaudio_t audio = (osaudio_t)pDevice->pUserData;
|
||||
|
||||
if (audio->info.direction == OSAUDIO_OUTPUT) {
|
||||
osaudio_data_callback_playback(audio, pOutput, frameCount);
|
||||
} else {
|
||||
osaudio_data_callback_capture(audio, pInput, frameCount);
|
||||
}
|
||||
}
|
||||
|
||||
osaudio_result_t osaudio_open(osaudio_t* audio, osaudio_config_t* config)
|
||||
{
|
||||
osaudio_result_t result;
|
||||
ma_context* pContext = NULL;
|
||||
ma_device_config deviceConfig;
|
||||
ma_device_info deviceInfo;
|
||||
int periodCount = 2;
|
||||
unsigned int iChannel;
|
||||
|
||||
if (audio != NULL) {
|
||||
*audio = NULL; /* Safety. */
|
||||
}
|
||||
|
||||
if (audio == NULL || config == NULL) {
|
||||
return OSAUDIO_INVALID_ARGS;
|
||||
}
|
||||
|
||||
pContext = osaudio_ref_context(); /* Will be unreferenced in osaudio_close(). */
|
||||
if (pContext == NULL) {
|
||||
return OSAUDIO_ERROR;
|
||||
}
|
||||
|
||||
*audio = (osaudio_t)calloc(1, sizeof(**audio));
|
||||
if (*audio == NULL) {
|
||||
osaudio_unref_context();
|
||||
return OSAUDIO_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
if (config->direction == OSAUDIO_OUTPUT) {
|
||||
deviceConfig = ma_device_config_init(ma_device_type_playback);
|
||||
deviceConfig.playback.format = osaudio_format_to_miniaudio(config->format);
|
||||
deviceConfig.playback.channels = (ma_uint32)config->channels;
|
||||
|
||||
if (config->channel_map[0] != OSAUDIO_CHANNEL_NONE) {
|
||||
for (iChannel = 0; iChannel < config->channels; iChannel += 1) {
|
||||
deviceConfig.playback.pChannelMap[iChannel] = osaudio_channel_to_miniaudio(config->channel_map[iChannel]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
deviceConfig = ma_device_config_init(ma_device_type_capture);
|
||||
deviceConfig.capture.format = osaudio_format_to_miniaudio(config->format);
|
||||
deviceConfig.capture.channels = (ma_uint32)config->channels;
|
||||
|
||||
if (config->channel_map[0] != OSAUDIO_CHANNEL_NONE) {
|
||||
for (iChannel = 0; iChannel < config->channels; iChannel += 1) {
|
||||
deviceConfig.capture.pChannelMap[iChannel] = osaudio_channel_to_miniaudio(config->channel_map[iChannel]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deviceConfig.sampleRate = (ma_uint32)config->rate;
|
||||
|
||||
/* If the buffer size is 0, we'll default to 10ms. */
|
||||
deviceConfig.periodSizeInFrames = (ma_uint32)config->buffer_size;
|
||||
if (deviceConfig.periodSizeInFrames == 0) {
|
||||
deviceConfig.periodSizeInMilliseconds = 10;
|
||||
}
|
||||
|
||||
deviceConfig.dataCallback = osaudio_data_callback;
|
||||
deviceConfig.pUserData = *audio;
|
||||
|
||||
if ((config->flags & OSAUDIO_FLAG_NO_REROUTING) != 0) {
|
||||
deviceConfig.wasapi.noAutoStreamRouting = MA_TRUE;
|
||||
}
|
||||
|
||||
if (config->notification != NULL) {
|
||||
deviceConfig.notificationCallback = osaudio_nofication_callback;
|
||||
}
|
||||
|
||||
result = osaudio_result_from_miniaudio(ma_device_init(pContext, &deviceConfig, &((*audio)->device)));
|
||||
if (result != OSAUDIO_SUCCESS) {
|
||||
free(*audio);
|
||||
osaudio_unref_context();
|
||||
return result;
|
||||
}
|
||||
|
||||
/* The input config needs to be updated with actual values. */
|
||||
if (config->direction == OSAUDIO_OUTPUT) {
|
||||
config->format = osaudio_format_from_miniaudio((*audio)->device.playback.format);
|
||||
config->channels = (unsigned int)(*audio)->device.playback.channels;
|
||||
|
||||
for (iChannel = 0; iChannel < config->channels; iChannel += 1) {
|
||||
config->channel_map[iChannel] = osaudio_channel_from_miniaudio((*audio)->device.playback.channelMap[iChannel]);
|
||||
}
|
||||
} else {
|
||||
config->format = osaudio_format_from_miniaudio((*audio)->device.capture.format);
|
||||
config->channels = (unsigned int)(*audio)->device.capture.channels;
|
||||
|
||||
for (iChannel = 0; iChannel < config->channels; iChannel += 1) {
|
||||
config->channel_map[iChannel] = osaudio_channel_from_miniaudio((*audio)->device.capture.channelMap[iChannel]);
|
||||
}
|
||||
}
|
||||
|
||||
config->rate = (unsigned int)(*audio)->device.sampleRate;
|
||||
|
||||
if (deviceConfig.periodSizeInFrames == 0) {
|
||||
if (config->direction == OSAUDIO_OUTPUT) {
|
||||
config->buffer_size = (int)(*audio)->device.playback.internalPeriodSizeInFrames;
|
||||
} else {
|
||||
config->buffer_size = (int)(*audio)->device.capture.internalPeriodSizeInFrames;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* The device object needs to have its local info built. We can get the ID and name from miniaudio. */
|
||||
result = osaudio_result_from_miniaudio(ma_device_get_info(&(*audio)->device, (*audio)->device.type, &deviceInfo));
|
||||
if (result == MA_SUCCESS) {
|
||||
memcpy((*audio)->info.id.data, &deviceInfo.id, sizeof((*audio)->info.id.data));
|
||||
memcpy((*audio)->info.name, deviceInfo.name, sizeof((*audio)->info.name));
|
||||
}
|
||||
|
||||
(*audio)->info.direction = config->direction;
|
||||
(*audio)->info.config_count = 1;
|
||||
(*audio)->info.configs = &(*audio)->config;
|
||||
(*audio)->config = *config;
|
||||
(*audio)->config.device_id = &(*audio)->info.id;
|
||||
|
||||
|
||||
/* We need a ring buffer. */
|
||||
result = osaudio_result_from_miniaudio(ma_pcm_rb_init(osaudio_format_to_miniaudio(config->format), (ma_uint32)config->channels, (ma_uint32)config->buffer_size * periodCount, NULL, NULL, &(*audio)->buffer));
|
||||
if (result != OSAUDIO_SUCCESS) {
|
||||
ma_device_uninit(&(*audio)->device);
|
||||
free(*audio);
|
||||
osaudio_unref_context();
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Now we need a semaphore to control access to the ring buffer to block read/write when necessary. */
|
||||
result = osaudio_result_from_miniaudio(ma_semaphore_init((config->direction == OSAUDIO_OUTPUT) ? periodCount : 0, &(*audio)->bufferSemaphore));
|
||||
if (result != OSAUDIO_SUCCESS) {
|
||||
ma_pcm_rb_uninit(&(*audio)->buffer);
|
||||
ma_device_uninit(&(*audio)->device);
|
||||
free(*audio);
|
||||
osaudio_unref_context();
|
||||
return result;
|
||||
}
|
||||
|
||||
return OSAUDIO_SUCCESS;
|
||||
}
|
||||
|
||||
osaudio_result_t osaudio_close(osaudio_t audio)
|
||||
{
|
||||
if (audio == NULL) {
|
||||
return OSAUDIO_INVALID_ARGS;
|
||||
}
|
||||
|
||||
ma_device_uninit(&audio->device);
|
||||
osaudio_unref_context();
|
||||
|
||||
return OSAUDIO_SUCCESS;
|
||||
}
|
||||
|
||||
static void osaudio_activate(osaudio_t audio)
|
||||
{
|
||||
ma_spinlock_lock(&audio->activateLock);
|
||||
{
|
||||
if (ma_atomic_bool32_get(&audio->isActive) == MA_FALSE) {
|
||||
ma_atomic_bool32_set(&audio->isActive, MA_TRUE);
|
||||
|
||||
/* If we need to flush, do so now before starting the device. */
|
||||
if (ma_atomic_bool32_get(&audio->isFlushed) == MA_TRUE) {
|
||||
ma_pcm_rb_reset(&audio->buffer);
|
||||
ma_atomic_bool32_set(&audio->isFlushed, MA_FALSE);
|
||||
}
|
||||
|
||||
/* If we're not paused, start the device. */
|
||||
if (ma_atomic_bool32_get(&audio->isPaused) == MA_FALSE) {
|
||||
ma_device_start(&audio->device);
|
||||
}
|
||||
}
|
||||
}
|
||||
ma_spinlock_unlock(&audio->activateLock);
|
||||
}
|
||||
|
||||
osaudio_result_t osaudio_write(osaudio_t audio, const void* data, unsigned int frame_count)
|
||||
{
|
||||
if (audio == NULL) {
|
||||
return OSAUDIO_INVALID_ARGS;
|
||||
}
|
||||
|
||||
ma_mutex_lock(&audio->drainLock);
|
||||
{
|
||||
/* Don't return until everything has been written. */
|
||||
while (frame_count > 0) {
|
||||
ma_uint32 framesToWrite = frame_count;
|
||||
ma_uint32 framesAvailableInBuffer;
|
||||
|
||||
/* There should be enough data available in the buffer now, but check anyway. */
|
||||
framesAvailableInBuffer = ma_pcm_rb_available_write(&audio->buffer);
|
||||
if (framesAvailableInBuffer > 0) {
|
||||
void* pBuffer;
|
||||
|
||||
if (framesToWrite > framesAvailableInBuffer) {
|
||||
framesToWrite = framesAvailableInBuffer;
|
||||
}
|
||||
|
||||
ma_pcm_rb_acquire_write(&audio->buffer, &framesToWrite, &pBuffer);
|
||||
{
|
||||
ma_copy_pcm_frames(pBuffer, data, framesToWrite, audio->device.playback.format, audio->device.playback.channels);
|
||||
}
|
||||
ma_pcm_rb_commit_write(&audio->buffer, framesToWrite);
|
||||
|
||||
frame_count -= (unsigned int)framesToWrite;
|
||||
data = (const void*)((const unsigned char*)data + (framesToWrite * ma_get_bytes_per_frame(audio->device.playback.format, audio->device.playback.channels)));
|
||||
|
||||
if (framesToWrite > 0) {
|
||||
osaudio_activate(audio);
|
||||
}
|
||||
} else {
|
||||
/* If we get here it means there's not enough data available in the buffer. We need to wait for more. */
|
||||
ma_semaphore_wait(&audio->bufferSemaphore);
|
||||
|
||||
/* If we're not active it probably means we've flushed. This write needs to be aborted. */
|
||||
if (ma_atomic_bool32_get(&audio->isActive) == MA_FALSE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ma_mutex_unlock(&audio->drainLock);
|
||||
|
||||
if ((audio->config.flags & OSAUDIO_FLAG_REPORT_XRUN) != 0) {
|
||||
if (ma_atomic_bool32_get(&audio->xrunDetected)) {
|
||||
ma_atomic_bool32_set(&audio->xrunDetected, MA_FALSE);
|
||||
return OSAUDIO_XRUN;
|
||||
}
|
||||
}
|
||||
|
||||
return OSAUDIO_SUCCESS;
|
||||
}
|
||||
|
||||
osaudio_result_t osaudio_read(osaudio_t audio, void* data, unsigned int frame_count)
|
||||
{
|
||||
if (audio == NULL) {
|
||||
return OSAUDIO_INVALID_ARGS;
|
||||
}
|
||||
|
||||
ma_mutex_lock(&audio->drainLock);
|
||||
{
|
||||
while (frame_count > 0) {
|
||||
ma_uint32 framesToRead = frame_count;
|
||||
ma_uint32 framesAvailableInBuffer;
|
||||
|
||||
/* There should be enough data available in the buffer now, but check anyway. */
|
||||
framesAvailableInBuffer = ma_pcm_rb_available_read(&audio->buffer);
|
||||
if (framesAvailableInBuffer > 0) {
|
||||
void* pBuffer;
|
||||
|
||||
if (framesToRead > framesAvailableInBuffer) {
|
||||
framesToRead = framesAvailableInBuffer;
|
||||
}
|
||||
|
||||
ma_pcm_rb_acquire_read(&audio->buffer, &framesToRead, &pBuffer);
|
||||
{
|
||||
ma_copy_pcm_frames(data, pBuffer, framesToRead, audio->device.capture.format, audio->device.capture.channels);
|
||||
}
|
||||
ma_pcm_rb_commit_read(&audio->buffer, framesToRead);
|
||||
|
||||
frame_count -= (unsigned int)framesToRead;
|
||||
data = (void*)((unsigned char*)data + (framesToRead * ma_get_bytes_per_frame(audio->device.capture.format, audio->device.capture.channels)));
|
||||
} else {
|
||||
/* Activate the device from the get go or else we'll never end up capturing anything. */
|
||||
osaudio_activate(audio);
|
||||
|
||||
/* If we get here it means there's not enough data available in the buffer. We need to wait for more. */
|
||||
ma_semaphore_wait(&audio->bufferSemaphore);
|
||||
|
||||
/* If we're not active it probably means we've flushed. This read needs to be aborted. */
|
||||
if (ma_atomic_bool32_get(&audio->isActive) == MA_FALSE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ma_mutex_unlock(&audio->drainLock);
|
||||
|
||||
if ((audio->config.flags & OSAUDIO_FLAG_REPORT_XRUN) != 0) {
|
||||
if (ma_atomic_bool32_get(&audio->xrunDetected)) {
|
||||
ma_atomic_bool32_set(&audio->xrunDetected, MA_FALSE);
|
||||
return OSAUDIO_XRUN;
|
||||
}
|
||||
}
|
||||
|
||||
return OSAUDIO_SUCCESS;
|
||||
}
|
||||
|
||||
osaudio_result_t osaudio_drain(osaudio_t audio)
|
||||
{
|
||||
if (audio == NULL) {
|
||||
return OSAUDIO_INVALID_ARGS;
|
||||
}
|
||||
|
||||
/* This cannot be called while the device is in a paused state. */
|
||||
if (ma_atomic_bool32_get(&audio->isPaused)) {
|
||||
return OSAUDIO_DEVICE_STOPPED;
|
||||
}
|
||||
|
||||
/* For capture we want to stop the device immediately or else we won't ever drain the buffer because miniaudio will be constantly filling it. */
|
||||
if (audio->info.direction == OSAUDIO_INPUT) {
|
||||
ma_device_stop(&audio->device);
|
||||
}
|
||||
|
||||
/*
|
||||
Mark the device as inactive *before* releasing the semaphore. When read/write completes waiting
|
||||
on the semaphore, they'll check this flag and abort.
|
||||
*/
|
||||
ma_atomic_bool32_set(&audio->isActive, MA_FALSE);
|
||||
|
||||
/*
|
||||
Again in capture mode, we need to release the semaphore before waiting for the drain lock because
|
||||
there's a chance read() will be waiting on the semaphore and will need to be woken up in order for
|
||||
it to be given to chance to return.
|
||||
*/
|
||||
if (audio->info.direction == OSAUDIO_INPUT) {
|
||||
ma_semaphore_release(&audio->bufferSemaphore);
|
||||
}
|
||||
|
||||
/* Now we need to wait for any pending reads or writes to complete. */
|
||||
ma_mutex_lock(&audio->drainLock);
|
||||
{
|
||||
/* No processing should be happening on the buffer at this point. Wait for miniaudio to consume the buffer. */
|
||||
while (ma_pcm_rb_available_read(&audio->buffer) > 0) {
|
||||
ma_sleep(1);
|
||||
}
|
||||
|
||||
/*
|
||||
At this point the buffer should be empty, and we shouldn't be in any read or write calls. If
|
||||
it's a playback device, we'll want to stop the device. There's no need to release the semaphore.
|
||||
*/
|
||||
if (audio->info.direction == OSAUDIO_OUTPUT) {
|
||||
ma_device_stop(&audio->device);
|
||||
}
|
||||
}
|
||||
ma_mutex_unlock(&audio->drainLock);
|
||||
|
||||
return OSAUDIO_SUCCESS;
|
||||
}
|
||||
|
||||
osaudio_result_t osaudio_flush(osaudio_t audio)
|
||||
{
|
||||
if (audio == NULL) {
|
||||
return OSAUDIO_INVALID_ARGS;
|
||||
}
|
||||
|
||||
/*
|
||||
First stop the device. This ensures the miniaudio background thread doesn't try modifying the
|
||||
buffer from under us while we're trying to flush it.
|
||||
*/
|
||||
ma_device_stop(&audio->device);
|
||||
|
||||
/*
|
||||
Mark the device as inactive *before* releasing the semaphore. When read/write completes waiting
|
||||
on the semaphore, they'll check this flag and abort.
|
||||
*/
|
||||
ma_atomic_bool32_set(&audio->isActive, MA_FALSE);
|
||||
|
||||
/*
|
||||
Release the semaphore after marking the device as inactive. This needs to be released in order
|
||||
to wakeup osaudio_read() and osaudio_write().
|
||||
*/
|
||||
ma_semaphore_release(&audio->bufferSemaphore);
|
||||
|
||||
/*
|
||||
The buffer should only be modified by osaudio_read() or osaudio_write(), or the miniaudio
|
||||
background thread. Therefore, we don't actually clear the buffer here. Instead we'll clear it
|
||||
in osaudio_activate(), depending on whether or not the below flag is set.
|
||||
*/
|
||||
ma_atomic_bool32_set(&audio->isFlushed, MA_TRUE);
|
||||
|
||||
return OSAUDIO_SUCCESS;
|
||||
}
|
||||
|
||||
osaudio_result_t osaudio_pause(osaudio_t audio)
|
||||
{
|
||||
osaudio_result_t result = OSAUDIO_SUCCESS;
|
||||
|
||||
if (audio == NULL) {
|
||||
return OSAUDIO_INVALID_ARGS;
|
||||
}
|
||||
|
||||
ma_spinlock_lock(&audio->activateLock);
|
||||
{
|
||||
if (ma_atomic_bool32_get(&audio->isPaused) == MA_FALSE) {
|
||||
ma_atomic_bool32_set(&audio->isPaused, MA_TRUE);
|
||||
|
||||
/* No need to stop the device if it's not active. */
|
||||
if (ma_atomic_bool32_get(&audio->isActive)) {
|
||||
result = osaudio_result_from_miniaudio(ma_device_stop(&audio->device));
|
||||
}
|
||||
}
|
||||
}
|
||||
ma_spinlock_unlock(&audio->activateLock);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
osaudio_result_t osaudio_resume(osaudio_t audio)
|
||||
{
|
||||
osaudio_result_t result = OSAUDIO_SUCCESS;
|
||||
|
||||
if (audio == NULL) {
|
||||
return OSAUDIO_INVALID_ARGS;
|
||||
}
|
||||
|
||||
ma_spinlock_lock(&audio->activateLock);
|
||||
{
|
||||
if (ma_atomic_bool32_get(&audio->isPaused)) {
|
||||
ma_atomic_bool32_set(&audio->isPaused, MA_FALSE);
|
||||
|
||||
/* Don't start the device unless it's active. */
|
||||
if (ma_atomic_bool32_get(&audio->isActive)) {
|
||||
result = osaudio_result_from_miniaudio(ma_device_start(&audio->device));
|
||||
}
|
||||
}
|
||||
}
|
||||
ma_spinlock_unlock(&audio->activateLock);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
unsigned int osaudio_get_avail(osaudio_t audio)
|
||||
{
|
||||
if (audio == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (audio->info.direction == OSAUDIO_OUTPUT) {
|
||||
return ma_pcm_rb_available_write(&audio->buffer);
|
||||
} else {
|
||||
return ma_pcm_rb_available_read(&audio->buffer);
|
||||
}
|
||||
}
|
||||
|
||||
const osaudio_info_t* osaudio_get_info(osaudio_t audio)
|
||||
{
|
||||
if (audio == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return &audio->info;
|
||||
}
|
||||
|
||||
#endif /* osaudio_miniaudio_c */
|
||||
196
thirdparty/miniaudio-0.11.24/extras/osaudio/tests/osaudio_deviceio.c
vendored
Normal file
196
thirdparty/miniaudio-0.11.24/extras/osaudio/tests/osaudio_deviceio.c
vendored
Normal file
@@ -0,0 +1,196 @@
|
||||
#include "../osaudio.h"
|
||||
|
||||
/* This example uses miniaudio for decoding audio files. */
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "../../../miniaudio.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define MODE_PLAYBACK 0
|
||||
#define MODE_CAPTURE 1
|
||||
#define MODE_DUPLEX 2
|
||||
|
||||
void enumerate_devices()
|
||||
{
|
||||
int result;
|
||||
unsigned int iDevice;
|
||||
unsigned int count;
|
||||
osaudio_info_t* pDeviceInfos;
|
||||
|
||||
result = osaudio_enumerate(&count, &pDeviceInfos);
|
||||
if (result != OSAUDIO_SUCCESS) {
|
||||
printf("Failed to enumerate audio devices.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
for (iDevice = 0; iDevice < count; iDevice += 1) {
|
||||
printf("(%s) %s\n", (pDeviceInfos[iDevice].direction == OSAUDIO_OUTPUT) ? "Playback" : "Capture", pDeviceInfos[iDevice].name);
|
||||
}
|
||||
|
||||
free(pDeviceInfos);
|
||||
}
|
||||
|
||||
osaudio_t open_device(int direction)
|
||||
{
|
||||
int result;
|
||||
osaudio_t audio;
|
||||
osaudio_config_t config;
|
||||
|
||||
osaudio_config_init(&config, direction);
|
||||
config.format = OSAUDIO_FORMAT_F32;
|
||||
config.channels = 2;
|
||||
config.rate = 48000;
|
||||
config.flags = OSAUDIO_FLAG_REPORT_XRUN;
|
||||
|
||||
result = osaudio_open(&audio, &config);
|
||||
if (result != OSAUDIO_SUCCESS) {
|
||||
printf("Failed to open audio device.\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return audio;
|
||||
}
|
||||
|
||||
void do_playback(int argc, char** argv)
|
||||
{
|
||||
int result;
|
||||
osaudio_t audio;
|
||||
const osaudio_config_t* config;
|
||||
const char* pFilePath = NULL;
|
||||
ma_result resultMA;
|
||||
ma_decoder_config decoderConfig;
|
||||
ma_decoder decoder;
|
||||
|
||||
audio = open_device(OSAUDIO_OUTPUT);
|
||||
if (audio == NULL) {
|
||||
printf("Failed to open audio device.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
config = &osaudio_get_info(audio)->configs[0];
|
||||
|
||||
/* We want to always use f32. */
|
||||
if (config->format == OSAUDIO_FORMAT_F32) {
|
||||
if (argc > 1) {
|
||||
pFilePath = argv[1];
|
||||
|
||||
decoderConfig = ma_decoder_config_init(ma_format_f32, (ma_uint32)config->channels, (ma_uint32)config->rate);
|
||||
|
||||
resultMA = ma_decoder_init_file(pFilePath, &decoderConfig, &decoder);
|
||||
if (resultMA == MA_SUCCESS) {
|
||||
/* Now just keep looping over each sample until we get to the end. */
|
||||
for (;;) {
|
||||
float frames[1024];
|
||||
ma_uint64 frameCount;
|
||||
|
||||
resultMA = ma_decoder_read_pcm_frames(&decoder, frames, ma_countof(frames) / config->channels, &frameCount);
|
||||
if (resultMA != MA_SUCCESS) {
|
||||
break;
|
||||
}
|
||||
|
||||
result = osaudio_write(audio, frames, (unsigned int)frameCount); /* Safe cast. */
|
||||
if (result != OSAUDIO_SUCCESS && result != OSAUDIO_XRUN) {
|
||||
printf("Error writing to audio device.");
|
||||
break;
|
||||
}
|
||||
|
||||
if (result == OSAUDIO_XRUN) {
|
||||
printf("WARNING: An xrun occurred while writing to the playback device.\n");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
printf("Failed to open file: %s\n", pFilePath);
|
||||
}
|
||||
} else {
|
||||
printf("No input file.\n");
|
||||
}
|
||||
} else {
|
||||
printf("Unsupported device format.\n");
|
||||
}
|
||||
|
||||
/* Getting here means we're done and we can tear down. */
|
||||
osaudio_close(audio);
|
||||
}
|
||||
|
||||
void do_duplex()
|
||||
{
|
||||
int result;
|
||||
osaudio_t capture;
|
||||
osaudio_t playback;
|
||||
|
||||
capture = open_device(OSAUDIO_INPUT);
|
||||
if (capture == NULL) {
|
||||
printf("Failed to open capture device.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
playback = open_device(OSAUDIO_OUTPUT);
|
||||
if (playback == NULL) {
|
||||
osaudio_close(capture);
|
||||
printf("Failed to open playback device.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
float frames[1024];
|
||||
unsigned int frameCount;
|
||||
|
||||
frameCount = ma_countof(frames) / osaudio_get_info(capture)->configs[0].channels;
|
||||
|
||||
/* Capture. */
|
||||
result = osaudio_read(capture, frames, frameCount);
|
||||
if (result != OSAUDIO_SUCCESS && result != OSAUDIO_XRUN) {
|
||||
printf("Error reading from capture device.\n");
|
||||
break;
|
||||
}
|
||||
|
||||
if (result == OSAUDIO_XRUN) {
|
||||
printf("WARNING: An xrun occurred while reading from the capture device.\n");
|
||||
}
|
||||
|
||||
|
||||
/* Playback. */
|
||||
result = osaudio_write(playback, frames, frameCount);
|
||||
if (result != OSAUDIO_SUCCESS && result != OSAUDIO_XRUN) {
|
||||
printf("Error writing to playback device.\n");
|
||||
break;
|
||||
}
|
||||
|
||||
if (result == OSAUDIO_XRUN) {
|
||||
printf("WARNING: An xrun occurred while writing to the playback device.\n");
|
||||
}
|
||||
}
|
||||
|
||||
osaudio_close(capture);
|
||||
osaudio_close(playback);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
int mode = MODE_PLAYBACK;
|
||||
int iarg;
|
||||
|
||||
enumerate_devices();
|
||||
|
||||
for (iarg = 0; iarg < argc; iarg += 1) {
|
||||
if (strcmp(argv[iarg], "capture") == 0) {
|
||||
mode = MODE_CAPTURE;
|
||||
} else if (strcmp(argv[iarg], "duplex") == 0) {
|
||||
mode = MODE_DUPLEX;
|
||||
}
|
||||
}
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case MODE_PLAYBACK: do_playback(argc, argv); break;
|
||||
case MODE_CAPTURE: break;
|
||||
case MODE_DUPLEX: do_duplex(); break;
|
||||
}
|
||||
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
return 0;
|
||||
}
|
||||
283
thirdparty/miniaudio-0.11.24/extras/osaudio/tests/osaudio_sine.c
vendored
Normal file
283
thirdparty/miniaudio-0.11.24/extras/osaudio/tests/osaudio_sine.c
vendored
Normal file
@@ -0,0 +1,283 @@
|
||||
#include "../osaudio.h"
|
||||
#include "../../decoders/litewav/litewav.c"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h> /* free() */
|
||||
|
||||
#if defined(__MSDOS__) || defined(__DOS__)
|
||||
#include <dos.h>
|
||||
#define OSAUDIO_DOS
|
||||
#endif
|
||||
|
||||
const char* format_to_string(osaudio_format_t format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case OSAUDIO_FORMAT_F32: return "F32";
|
||||
case OSAUDIO_FORMAT_U8: return "U8";
|
||||
case OSAUDIO_FORMAT_S16: return "S16";
|
||||
case OSAUDIO_FORMAT_S24: return "S24";
|
||||
case OSAUDIO_FORMAT_S32: return "S32";
|
||||
default: return "Unknown Format";
|
||||
}
|
||||
}
|
||||
|
||||
void enumerate_devices()
|
||||
{
|
||||
osaudio_result_t result;
|
||||
osaudio_info_t* pDeviceInfos;
|
||||
unsigned int deviceCount;
|
||||
unsigned int iDevice;
|
||||
|
||||
result = osaudio_enumerate(&deviceCount, &pDeviceInfos);
|
||||
if (result != OSAUDIO_SUCCESS) {
|
||||
printf("Failed to enumerate devices.");
|
||||
return;
|
||||
}
|
||||
|
||||
for (iDevice = 0; iDevice < deviceCount; iDevice += 1) {
|
||||
osaudio_info_t* pDeviceInfo = &pDeviceInfos[iDevice];
|
||||
|
||||
printf("Device %u: [%s] %s\n", iDevice, (pDeviceInfo->direction == OSAUDIO_OUTPUT) ? "Playback" : "Capture", pDeviceInfo->name);
|
||||
|
||||
#if 0
|
||||
{
|
||||
unsigned int iFormat;
|
||||
|
||||
printf(" Native Formats\n");
|
||||
for (iFormat = 0; iFormat < pDeviceInfo->config_count; iFormat += 1) {
|
||||
osaudio_config_t* pConfig = &pDeviceInfo->configs[iFormat];
|
||||
printf(" %s %uHz %u channels\n", format_to_string(pConfig->format), pConfig->rate, pConfig->channels);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
free(pDeviceInfos);
|
||||
}
|
||||
|
||||
extern int g_TESTING;
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/* Sine wave generation. */
|
||||
#include <math.h>
|
||||
|
||||
#if defined(OSAUDIO_DOS)
|
||||
/* For farmalloc(). */
|
||||
static void OSAUDIO_FAR* far_malloc(unsigned int sz)
|
||||
{
|
||||
unsigned int segment;
|
||||
unsigned int err;
|
||||
|
||||
err = _dos_allocmem(sz >> 4, &segment);
|
||||
if (err == 0) {
|
||||
return MK_FP(segment, 0);
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
#else
|
||||
#define far_malloc malloc
|
||||
#endif
|
||||
|
||||
static char OSAUDIO_FAR* gen_sine_u8(unsigned long frameCount, unsigned int channels, unsigned int sampleRate)
|
||||
{
|
||||
float phase = 0;
|
||||
float phaseIncrement = 2 * 3.14159265f * 220.0f / 44100.0f;
|
||||
unsigned long iFrame;
|
||||
char OSAUDIO_FAR* pData;
|
||||
char OSAUDIO_FAR* pRunningData;
|
||||
|
||||
pData = (char OSAUDIO_FAR*)far_malloc(frameCount * channels);
|
||||
if (pData == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pRunningData = pData;
|
||||
|
||||
for (iFrame = 0; iFrame < frameCount; iFrame += 1) {
|
||||
unsigned int iChannel;
|
||||
float sample = (float)sin(phase) * 0.2f;
|
||||
sample = (sample + 1.0f) * 127.5f;
|
||||
|
||||
for (iChannel = 0; iChannel < channels; iChannel += 1) {
|
||||
pRunningData[iChannel] = (unsigned char)sample;
|
||||
}
|
||||
|
||||
pRunningData += channels;
|
||||
phase += phaseIncrement;
|
||||
}
|
||||
|
||||
return pData;
|
||||
}
|
||||
|
||||
static short OSAUDIO_FAR* gen_sine_s16(unsigned long frameCount, unsigned int channels, unsigned int sampleRate)
|
||||
{
|
||||
float phase = 0;
|
||||
float phaseIncrement = 2 * 3.14159265f * 220.0f / 44100.0f;
|
||||
unsigned long iFrame;
|
||||
short OSAUDIO_FAR* pData;
|
||||
short OSAUDIO_FAR* pRunningData;
|
||||
|
||||
pData = (short OSAUDIO_FAR*)far_malloc(frameCount * channels * sizeof(short));
|
||||
if (pData == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pRunningData = pData;
|
||||
|
||||
for (iFrame = 0; iFrame < frameCount; iFrame += 1) {
|
||||
unsigned int iChannel;
|
||||
float sample = (float)sin(phase) * 0.2f;
|
||||
sample = sample * 32767.5f;
|
||||
|
||||
for (iChannel = 0; iChannel < channels; iChannel += 1) {
|
||||
pRunningData[iChannel] = (short)sample;
|
||||
}
|
||||
|
||||
pRunningData += channels;
|
||||
phase += phaseIncrement;
|
||||
}
|
||||
|
||||
return pData;
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//float sinePhase = 0;
|
||||
//float sinePhaseIncrement = 0;
|
||||
//float sineVolume = 0.2f;
|
||||
//
|
||||
//static void sine_init()
|
||||
//{
|
||||
// sinePhase = 0;
|
||||
// sinePhaseIncrement = 2 * 3.14159265f * 440.0f / 44100.0f;
|
||||
//}
|
||||
//
|
||||
//static void sine_u8(unsigned char* dst, unsigned int frameCount, unsigned int channels)
|
||||
//{
|
||||
// unsigned int iFrame;
|
||||
//
|
||||
// for (iFrame = 0; iFrame < frameCount; iFrame += 1) {
|
||||
// unsigned int iChannel;
|
||||
// float sample = (float)sin(sinePhase) * sineVolume;
|
||||
// sample = (sample + 1.0f) * 127.5f;
|
||||
//
|
||||
// for (iChannel = 0; iChannel < channels; iChannel += 1) {
|
||||
// dst[iChannel] = (unsigned char)sample;
|
||||
// }
|
||||
//
|
||||
// dst += channels;
|
||||
// sinePhase += sinePhaseIncrement;
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//
|
||||
//unsigned char data[4096];
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
osaudio_result_t result;
|
||||
osaudio_t audio;
|
||||
osaudio_config_t config;
|
||||
void OSAUDIO_FAR* pSineWave;
|
||||
unsigned long sineWaveFrameCount;
|
||||
unsigned long sineWaveCursor = 0;
|
||||
|
||||
enumerate_devices();
|
||||
|
||||
osaudio_config_init(&config, OSAUDIO_OUTPUT);
|
||||
config.format = OSAUDIO_FORMAT_S16;
|
||||
config.channels = 2;
|
||||
config.rate = 44100;
|
||||
|
||||
result = osaudio_open(&audio, &config);
|
||||
if (result != OSAUDIO_SUCCESS) {
|
||||
printf("Failed to initialize audio.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf("Device: %s (%s %uHz %u channels)\n", osaudio_get_info(audio)->name, format_to_string(config.format), config.rate, config.channels);
|
||||
|
||||
//printf("sizeof(void*) = %u\n", (unsigned int)sizeof(void far *));
|
||||
|
||||
/* 5 seconds. */
|
||||
sineWaveFrameCount = config.rate * 1;
|
||||
|
||||
if (config.format == OSAUDIO_FORMAT_U8) {
|
||||
pSineWave = gen_sine_u8(sineWaveFrameCount, config.channels, config.rate);
|
||||
} else {
|
||||
pSineWave = gen_sine_s16(sineWaveFrameCount, config.channels, config.rate);
|
||||
}
|
||||
|
||||
if (pSineWave == NULL) {
|
||||
printf("Failed to generate sine wave.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (config.format == OSAUDIO_FORMAT_U8) {
|
||||
/*unsigned int framesToSilence = config.rate;
|
||||
while (framesToSilence > 0) {
|
||||
unsigned int framesToWrite;
|
||||
char silence[256];
|
||||
memset(silence, 128, sizeof(silence));
|
||||
|
||||
framesToWrite = framesToSilence;
|
||||
if (framesToWrite > sizeof(silence) / config.channels) {
|
||||
framesToWrite = sizeof(silence) / config.channels;
|
||||
}
|
||||
|
||||
osaudio_write(audio, silence, framesToWrite);
|
||||
framesToSilence -= framesToWrite;
|
||||
}*/
|
||||
|
||||
while (sineWaveCursor < sineWaveFrameCount) {
|
||||
unsigned long framesToWrite = sineWaveFrameCount - sineWaveCursor;
|
||||
if (framesToWrite > 0xFFFF) {
|
||||
framesToWrite = 0xFFFF;
|
||||
}
|
||||
|
||||
//printf("Writing sine wave: %u\n", (unsigned int)framesToWrite);
|
||||
|
||||
osaudio_write(audio, (char OSAUDIO_FAR*)pSineWave + (sineWaveCursor * config.channels), (unsigned int)framesToWrite);
|
||||
sineWaveCursor += framesToWrite;
|
||||
|
||||
//printf("TRACE 0\n");
|
||||
//sine_u8(data, frameCount, config.channels);
|
||||
//printf("TRACE: %d\n", frameCount);
|
||||
//osaudio_write(audio, data, frameCount);
|
||||
//printf("DONE LOOP\n");
|
||||
}
|
||||
} else if (config.format == OSAUDIO_FORMAT_S16) {
|
||||
while (sineWaveCursor < sineWaveFrameCount) {
|
||||
unsigned long framesToWrite = sineWaveFrameCount - sineWaveCursor;
|
||||
if (framesToWrite > 0xFFFF) {
|
||||
framesToWrite = 0xFFFF;
|
||||
}
|
||||
|
||||
osaudio_write(audio, (short OSAUDIO_FAR*)pSineWave + (sineWaveCursor * config.channels), (unsigned int)framesToWrite);
|
||||
sineWaveCursor += framesToWrite;
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(OSAUDIO_DOS)
|
||||
printf("Processing...\n");
|
||||
for (;;) {
|
||||
/* Temporary. Just spinning here to ensure the program stays active. */
|
||||
//delay(1);
|
||||
if (g_TESTING > 0) {
|
||||
//printf("TESTING: %d\n", g_TESTING);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
printf("Shutting down... ");
|
||||
osaudio_close(audio);
|
||||
printf("Done.\n");
|
||||
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user