diff --git a/extras/osaudio/README.md b/extras/osaudio/README.md deleted file mode 100644 index 71328810..00000000 --- a/extras/osaudio/README.md +++ /dev/null @@ -1,49 +0,0 @@ -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. diff --git a/extras/osaudio/osaudio.h b/extras/osaudio/osaudio.h deleted file mode 100644 index 84fc0ca7..00000000 --- a/extras/osaudio/osaudio.h +++ /dev/null @@ -1,604 +0,0 @@ -/* -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 */ diff --git a/extras/osaudio/osaudio_dos_soundblaster.c b/extras/osaudio/osaudio_dos_soundblaster.c deleted file mode 100644 index 404dc71b..00000000 --- a/extras/osaudio/osaudio_dos_soundblaster.c +++ /dev/null @@ -1,1141 +0,0 @@ -/* -This is only designed to work on DOS. I have only tested compiling this with OpenWatcom v2.0. Open -to feedback on improving compiler compatibility. - -This will look at the BLASTER environment variable for the base port, IRQ and DMA channel. We're -only allowing a single device to be initialized at any given time. The channel will be defined by -the BLASTER environment variable, or if that's not set, it will default to channel 1 (for 8-bit) or -channel 5 (for 16-bit). -*/ - -/* Keep this file empty if we're not compiling for DOS. */ -#if defined(__MSDOS__) || defined(__DOS__) -#ifndef osaudio_dos_soundblaster_c -#define osaudio_dos_soundblaster_c - -#include "osaudio.h" - -#include /* outportb() */ -#include /* delay() */ -#include /* memset() */ -#include /* malloc(), free() */ - -int g_TESTING = 0; - -#define OSAUDIO_TIMEOUT_TICKS 18 /* ~1 second timeout (just under - runs at 18.2 ticks per second). Sound Blaster specs claim it should only take about 100 microseconds so this is way overkill. */ - -#define OSAUDIO_SB_MIXER_PORT 0x004 -#define OSAUDIO_SB_MIXER_DATA_PORT 0x005 -#define OSAUDIO_SB_DSP_RESET_PORT 0x006 -#define OSAUDIO_SB_DSP_READ_PORT 0x00A -#define OSAUDIO_SB_DSP_WRITE_PORT 0x00C -#define OSAUDIO_SB_DSP_READY_READ_PORT 0x00E - -#define OSAUDIO_SB_DSP_RESET_CMD 0x01 -#define OSAUDIO_SB_DSP_GET_VERSION 0xE1 - -#define OSAUDIO_ISA_DMA_MASK_REGISTER_8BIT 0x0A -#define OSAUDIO_ISA_DMA_MASK_REGISTER_16BIT 0xD4 - -#define OSAUDIO_ISA_DMA_FLIPFLOP_REGISTER_8BIT 0x0C -#define OSAUDIO_ISA_DMA_FLIPFLOP_REGISTER_16BIT 0xD8 - -#define OSAUDIO_ISA_DMA_MODE_REGISTER_8BIT 0x0B -#define OSAUDIO_ISA_DMA_MODE_REGISTER_16BIT 0xD6 -#define OSAUDIO_ISA_DMA_MODE_DEMAND 0x00 -#define OSAUDIO_ISA_DMA_MODE_SINGLE 0x40 -#define OSAUDIO_ISA_DMA_MODE_BLOCK 0x80 -#define OSAUDIO_ISA_DMA_MODE_CASCADE 0xC0 -#define OSAUDIO_ISA_DMA_MODE_READ 0x08 -#define OSAUDIO_ISA_DMA_MODE_WRITE 0x04 -#define OSAUDIO_ISA_DMA_MODE_AUTOINIT 0x10 - -#define OSAUDIO_ISA_DMA_ADDRESS_REGISTER_8BIT 0x00 -#define OSAUDIO_ISA_DMA_ADDRESS_REGISTER_16BIT 0xC0 - -#define OSAUDIO_ISA_DMA_COUNT_REGISTER_8BIT 0x01 -#define OSAUDIO_ISA_DMA_COUNT_REGISTER_16BIT 0xC2 - -#define OSAUDIO_SB_DEVICE_NAME "Sound Blaster" - -#define OSAUDIO_COUNTOF(x) (sizeof(x) / sizeof(x[0])) -#define OSAUDIO_ALIGN(x, a) (((x) + ((a)-1)) & ~((a)-1)) -#define OSAUDIO_ALIGN_32(x) OSAUDIO_ALIGN(x, 4) - -/* BLASTER environment variable defaults. */ -static unsigned short OSAUDIO_SB_BASE_PORT = 0x220; -static unsigned short OSAUDIO_SB_IRQ = 7; -static unsigned short OSAUDIO_SB_DMA_CHANNEL_8 = 1; -static unsigned short OSAUDIO_SB_DMA_CHANNEL_16 = 5; - -static signed char osaudio_g_is_sb16_present = -1; -static unsigned char osaudio_g_sb16_version_major = 0; -static unsigned char osaudio_g_sb16_version_minor = 0; -static osaudio_t osaudio_g_audio = NULL; /* For ISA DMA interrupt because there's no user data option. Only one device can be initialized at a time. */ - -static osaudio_format_t osaudio_g_supportedFormats[] = -{ - OSAUDIO_FORMAT_S16, OSAUDIO_FORMAT_U8 -}; - -static unsigned char osaudio_g_supportedChannels[] = -{ - 2, 1 -}; - -static unsigned int osaudio_g_supportedSampleRates[] = -{ - 44100, 22050, 11025, 24000, 12000, 8000 -}; - - -struct _osaudio_t -{ - void far* pDMABuffer; - void (_interrupt _far* old_isr)(void); - osaudio_info_t info; - osaudio_config_t config; /* info.configs will point to this. */ - unsigned int cursor; /* The position of the write or read cursor relative to the start of the current sub-buffer. In frames. */ - unsigned char subBufferIndex; /* When 0, the next write and read will happen in the first half of the DMA buffer, when 1, the second half. Will flip-flop between 0 and 1 each interrupt. */ - unsigned char isActive; - unsigned char isPaused; -}; - -static void osaudio_outportb(unsigned short port, unsigned char value) -{ - _outp(port, value); -} - -static unsigned char osaudio_inportb(unsigned short port) -{ - return _inp(port); -} - -static void osaudio_delay(unsigned int milliseconds) -{ - delay(milliseconds); -} - -static unsigned long osaudio_get_ticks() -{ - union REGS regs; - - regs.h.ah = 0x00; /* Get system time */ - int86(0x1A, ®s, ®s); - - return ((unsigned long)regs.x.cx << 16) | regs.x.dx; -} - -void far_memset(void far* dst, int c, unsigned int count) -{ - unsigned char far *p = dst; - unsigned int i; - - for (i = 0; i < count; i += 1) { - p[i] = c; - } -} - -static void far* osaudio_dos_calloc(unsigned int sz) -{ - /* This uses the DOS interrupt 0x21, function 0x48 allocation. */ - void far* p; - union REGS regs; - - regs.h.ah = 0x48; - regs.x.bx = (sz + 15) / 16; - int86(0x21, ®s, ®s); - - if (regs.x.cflag) { - return NULL; - } - - p = MK_FP(regs.x.ax, 0); - - /* Clear to zero for safety. */ - far_memset(p, 0, sz); - - return p; -} - -static void osaudio_dos_free(void far* p) -{ - /* This uses the DOS interrupt 0x21, function 0x49 free. */ - union REGS regs; - struct SREGS sregs; - - regs.h.ah = 0x49; - regs.x.bx = FP_OFF(p); - sregs.es = FP_SEG(p); - intdosx(®s, ®s, &sregs); -} - -static void osaudio_blaster_parse_env() -{ - /* This parses the BLASTER environment variable. */ - char* pBlaster; - char* p; - int unused; - - pBlaster = getenv("BLASTER"); - if (pBlaster == NULL) { - return; - } - - /* We have a BLASTER environment variable. */ - p = pBlaster; - - /* - I'm not sure if we can assume that each of these are present and in the same order. Therefore - we will do this generically and parse each segment, separated by whitespace. - */ - for (;;) { - /* Skip whitespace. */ - while (*p == ' ' || *p == '\t') { - p += 1; - } - - if (*p == '\0') { - break; - } - - /* Parse the segment. */ - if (p[0] == 'A') { - /* Base port. */ - p += 1; - OSAUDIO_SB_BASE_PORT = (unsigned short)strtoul(p, &p, 16); - /*printf("A%u\n", (unsigned int)OSAUDIO_SB_BASE_PORT);*/ - } else if (p[0] == 'I') { - /* IRQ. */ - p += 1; - OSAUDIO_SB_IRQ = (unsigned short)strtoul(p, &p, 10); - /*printf("I%u\n", (unsigned int)OSAUDIO_SB_IRQ);*/ - } else if (p[0] == 'D') { - /* 8-bit DMA channel. */ - p += 1; - OSAUDIO_SB_DMA_CHANNEL_8 = (unsigned short)strtoul(p, &p, 10); - /*printf("D%u\n", (unsigned int)OSAUDIO_SB_DMA_CHANNEL_8);*/ - } else if (p[0] == 'H') { - /* 16-bit DMA channel. */ - p += 1; - OSAUDIO_SB_DMA_CHANNEL_16 = (unsigned short)strtoul(p, &p, 10); - /*printf("H%u\n", (unsigned int)OSAUDIO_SB_DMA_CHANNEL_16);*/ - } else if (p[0] == 'M' || p[0] == 'P' || p[0] == 'T') { - /* These are ignored. */ - p += 1; - unused = (int)strtoul(p, &p, 16); - } else { - /* Unknown segment. Skip. */ - p += 1; - unused = (int)strtoul(p, &p, 16); - } - } -} - -static osaudio_result_t osaudio_init_sb16() -{ - /* This checks for the presence of a Sound Blaster 16 card. */ - if (osaudio_g_is_sb16_present == -1) { - unsigned long timeoutStart; - - /* - Creative wants us to read settings from the BLASTER environment variable. We don't hard - fail here - we'll just fall back to defaults. It'll fail later if we don't have Sound - Blaster available. - */ - osaudio_blaster_parse_env(); - - - /* Getting here means we need to check for SB16. */ - osaudio_outportb(OSAUDIO_SB_BASE_PORT | OSAUDIO_SB_DSP_RESET_PORT, OSAUDIO_SB_DSP_RESET_CMD); - osaudio_delay(1); /* Sound Blaster documentation says to wait 3 microseconds. We'll do 1 milliseconds. */ - osaudio_outportb(OSAUDIO_SB_BASE_PORT | OSAUDIO_SB_DSP_RESET_PORT, 0x00); - - /* Wait for DSP to be ready. */ - timeoutStart = osaudio_get_ticks(); - while ((osaudio_inportb(OSAUDIO_SB_BASE_PORT | OSAUDIO_SB_DSP_READY_READ_PORT) & 0x80) == 0) { - if (osaudio_get_ticks() - timeoutStart > OSAUDIO_TIMEOUT_TICKS) { - osaudio_g_is_sb16_present = 0; - return OSAUDIO_ERROR; - } - } - - /* Check result of reset. */ - if (osaudio_inportb(OSAUDIO_SB_BASE_PORT | OSAUDIO_SB_DSP_READ_PORT) == 0xAA) { - /* Wait for write port to be ready. */ - timeoutStart = osaudio_get_ticks(); - while (osaudio_inportb(OSAUDIO_SB_BASE_PORT | OSAUDIO_SB_DSP_WRITE_PORT) & 0x80) { - if (osaudio_get_ticks() - timeoutStart > OSAUDIO_TIMEOUT_TICKS) { - osaudio_g_is_sb16_present = 0; - return OSAUDIO_ERROR; - } - } - - /* Send DSP command to get version. */ - osaudio_outportb(OSAUDIO_SB_BASE_PORT | OSAUDIO_SB_DSP_WRITE_PORT, OSAUDIO_SB_DSP_GET_VERSION); - osaudio_g_sb16_version_major = osaudio_inportb(OSAUDIO_SB_BASE_PORT | OSAUDIO_SB_DSP_READ_PORT); - osaudio_g_sb16_version_minor = osaudio_inportb(OSAUDIO_SB_BASE_PORT | OSAUDIO_SB_DSP_READ_PORT); - - if (osaudio_g_sb16_version_major == 4) { - osaudio_g_is_sb16_present = 1; - } else { - osaudio_g_is_sb16_present = 0; - } - - /* Now configure the IRQ. */ - if (osaudio_g_is_sb16_present) { - unsigned char irqCode; - - switch (OSAUDIO_SB_IRQ) { - case 2: irqCode = 0x01; break; - case 5: irqCode = 0x02; break; - case 7: irqCode = 0x04; break; - case 10: irqCode = 0x08; break; - default: irqCode = 0x04; break; /* Will never hit this. */ - } - - /* - printf("Mixer port: %u\n", OSAUDIO_SB_BASE_PORT + OSAUDIO_SB_MIXER_PORT); - printf("Mixer data port: %u\n", OSAUDIO_SB_BASE_PORT + OSAUDIO_SB_MIXER_DATA_PORT); - printf("IRQ code: %u\n", (unsigned int)irqCode); - */ - - osaudio_outportb(OSAUDIO_SB_BASE_PORT + OSAUDIO_SB_MIXER_PORT, 0x80); - osaudio_outportb(OSAUDIO_SB_BASE_PORT + OSAUDIO_SB_MIXER_DATA_PORT, irqCode); - } - } else { - osaudio_g_is_sb16_present = 0; - } - } - - if (osaudio_g_is_sb16_present) { - return OSAUDIO_SUCCESS; - } else { - return OSAUDIO_ERROR; /* Don't appear to have SB16. */ - } -} - -osaudio_result_t osaudio_enumerate(unsigned int* count, osaudio_info_t** info) -{ - /* - We need only report a default playback device and a default capture device. We're going to - support both OSAUDIO_FORMAT_U8 and OSAUDIO_FORMAT_S16. Supported channel counts are mono and - stereo. - */ - osaudio_result_t result; - unsigned int nativeFormatCount; - unsigned int iSupportedFormat; - unsigned int iSupportedChannels; - unsigned int iSupportedSampleRate; - osaudio_config_t* pRunningConfig; - - if (count == NULL || info == NULL) { - return OSAUDIO_INVALID_ARGS; - } - - result = osaudio_init_sb16(); - if (result != OSAUDIO_SUCCESS) { - return result; - } - - nativeFormatCount = OSAUDIO_COUNTOF(osaudio_g_supportedFormats) * OSAUDIO_COUNTOF(osaudio_g_supportedChannels) * OSAUDIO_COUNTOF(osaudio_g_supportedSampleRates); - - *info = (osaudio_info_t*)calloc(1, sizeof(osaudio_info_t) * 2 + (sizeof(osaudio_config_t) * 2 * nativeFormatCount)); - if (*info == NULL) { - return OSAUDIO_OUT_OF_MEMORY; - } - - *count = 2; - - /* Now we need to fill out the details. */ - pRunningConfig = (osaudio_config_t*)(*info + 2); - - /* Playback. */ - strcpy((*info)[0].name, OSAUDIO_SB_DEVICE_NAME); - (*info)[0].direction = OSAUDIO_OUTPUT; - (*info)[0].config_count = nativeFormatCount; - (*info)[0].configs = pRunningConfig; - - for (iSupportedFormat = 0; iSupportedFormat < OSAUDIO_COUNTOF(osaudio_g_supportedFormats); iSupportedFormat += 1) { - for (iSupportedChannels = 0; iSupportedChannels < OSAUDIO_COUNTOF(osaudio_g_supportedChannels); iSupportedChannels += 1) { - for (iSupportedSampleRate = 0; iSupportedSampleRate < OSAUDIO_COUNTOF(osaudio_g_supportedSampleRates); iSupportedSampleRate += 1) { - osaudio_config_init(pRunningConfig, OSAUDIO_OUTPUT); - pRunningConfig->format = osaudio_g_supportedFormats[iSupportedFormat]; - pRunningConfig->channels = osaudio_g_supportedChannels[iSupportedChannels]; - pRunningConfig->rate = osaudio_g_supportedSampleRates[iSupportedSampleRate]; - - if (pRunningConfig->channels == 1) { - pRunningConfig->channel_map[0] = OSAUDIO_CHANNEL_MONO; - } else { - pRunningConfig->channel_map[0] = OSAUDIO_CHANNEL_FL; - pRunningConfig->channel_map[1] = OSAUDIO_CHANNEL_FR; - } - - pRunningConfig += 1; - } - } - } - - /* Capture. */ - strcpy((*info)[1].name, OSAUDIO_SB_DEVICE_NAME); - (*info)[1].direction = OSAUDIO_INPUT; - (*info)[1].config_count = nativeFormatCount; - (*info)[1].configs = pRunningConfig; - - for (iSupportedFormat = 0; iSupportedFormat < OSAUDIO_COUNTOF(osaudio_g_supportedFormats); iSupportedFormat += 1) { - for (iSupportedChannels = 0; iSupportedChannels < OSAUDIO_COUNTOF(osaudio_g_supportedChannels); iSupportedChannels += 1) { - for (iSupportedSampleRate = 0; iSupportedSampleRate < OSAUDIO_COUNTOF(osaudio_g_supportedSampleRates); iSupportedSampleRate += 1) { - osaudio_config_init(pRunningConfig, OSAUDIO_OUTPUT); - pRunningConfig->format = osaudio_g_supportedFormats[iSupportedFormat]; - pRunningConfig->channels = osaudio_g_supportedChannels[iSupportedChannels]; - pRunningConfig->rate = osaudio_g_supportedSampleRates[iSupportedSampleRate]; - - if (pRunningConfig->channels == 1) { - pRunningConfig->channel_map[0] = OSAUDIO_CHANNEL_MONO; - } else { - pRunningConfig->channel_map[0] = OSAUDIO_CHANNEL_FL; - pRunningConfig->channel_map[1] = OSAUDIO_CHANNEL_FR; - } - - pRunningConfig += 1; - } - } - } - - return OSAUDIO_SUCCESS; -} - -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 unsigned int osaudio_find_closest_rate(unsigned int rate, const unsigned int* pAvailableRates, unsigned int availableRateCount) -{ - unsigned int iRate; - unsigned int bestRate; - unsigned int bestRateDelta; - unsigned int rateDelta; - - bestRate = 0; - bestRateDelta = 0xFFFFFFFF; - for (iRate = 0; iRate < availableRateCount; iRate += 1) { - rateDelta = (pAvailableRates[iRate] > rate) ? (pAvailableRates[iRate] - rate) : (rate - pAvailableRates[iRate]); - if (rateDelta < bestRateDelta) { - bestRate = pAvailableRates[iRate]; - bestRateDelta = rateDelta; - } - } - - return bestRate; -} - - - -#define PIC1_COMMAND 0x20 -#define PIC1_DATA 0x21 -#define PIC2_COMMAND 0xA0 -#define PIC2_DATA 0xA1 -#define PIC_EOI 0x20 - -static void send_eoi(int irq) -{ - if (irq >= 8) { - /* If it's IRQ8 or higher, we have to send an EOI to both the master and slave controllers */ - osaudio_outportb(PIC2_COMMAND, PIC_EOI); - } - - /* Always send an EOI to the master controller */ - osaudio_outportb(PIC1_COMMAND, PIC_EOI); -} - - -static void interrupt osaudio_isa_dma_interrupt_handler() -{ - unsigned char status; - unsigned int cursor; - - /* Unfortunately there's no user-data associated with the interrupt handler, so we have to use a global. */ - osaudio_t audio = osaudio_g_audio; - if (audio == NULL) { - return; - } - - /* In 16-bit mode, we need to check if the interrupt is for us. Only applies to SB16. */ -#if 0 - if (osaudio_g_sb16_version_major == 4) { - if (audio->config.format == OSAUDIO_FORMAT_S16) { - osaudio_outportb(OSAUDIO_SB_BASE_PORT + OSAUDIO_SB_MIXER_PORT, 0x82); - status = osaudio_inportb(OSAUDIO_SB_BASE_PORT + OSAUDIO_SB_MIXER_DATA_PORT); - if ((status & 0x02) == 0) { - return; /* Not for us. */ - } - } - } -#endif - - /* - g_TESTING += 1; - printf("TESTING: %d\n", g_TESTING); - */ - - cursor = audio->cursor; - - if (cursor < audio->config.buffer_size) { - /* This is an xrun. */ - unsigned int bytesPerFrame; - bytesPerFrame = audio->config.channels * ((audio->config.format == OSAUDIO_FORMAT_S16) ? 2 : 1); - - /* Fill the rest of the buffer with silence. */ - //far_memset((char OSAUDIO_FAR*)audio->pDMABuffer + (audio->subBufferIndex * audio->config.buffer_size * bytesPerFrame) + (cursor * bytesPerFrame), 0, (audio->config.buffer_size - cursor) * bytesPerFrame); - //far_memset((char OSAUDIO_FAR*)audio->pDMABuffer + (audio->subBufferIndex * audio->config.buffer_size * bytesPerFrame), 0, audio->config.buffer_size * bytesPerFrame); - - /* Now fill the entire next buffer with silence as well. */ - //far_memset((char OSAUDIO_FAR*)audio->pDMABuffer + ((1 - audio->subBufferIndex) * audio->config.buffer_size * bytesPerFrame), 0, audio->config.buffer_size * bytesPerFrame); - - far_memset((char OSAUDIO_FAR*)audio->pDMABuffer, 0, audio->config.buffer_size * bytesPerFrame * 2); - - printf("XRUN: cursor = %u, subBufferIndex = %u; test = %u\n", cursor, audio->subBufferIndex, (audio->config.buffer_size * bytesPerFrame * 2)); - return; - } - - /* Flip the sub-buffer index. */ - printf("Interrupt: sub-buffer index: %u\n", audio->subBufferIndex); - audio->subBufferIndex = 1 - audio->subBufferIndex; - - /* Make sure the cursor is reset in prepration for the next half of the buffer. */ - audio->cursor = 0; - - /* Mark the sub-buffer as consumed. */ - //audio->subBufferConsumed[audio->subBufferIndexInterrupt] = 1; - //audio->subBufferIndexInterrupt = 1 - audio->subBufferIndexInterrupt; - - /* Interrupt acknowledgment. */ - if (audio->config.format == OSAUDIO_FORMAT_U8) { - status = osaudio_inportb(OSAUDIO_SB_BASE_PORT + 0x00E); - } else { - status = osaudio_inportb(OSAUDIO_SB_BASE_PORT + 0x00F); - } - - //printf("status: 0x%02X\n", status); - (void)status; - - send_eoi(OSAUDIO_SB_IRQ); -} - - - -#include - -void fill_random_unsigned_chars(unsigned char far* buffer, unsigned int size) -{ - unsigned int i; - - /* Seed the random number generator */ - srand((unsigned)time(NULL)); - - for (i = 0; i < size; i++) { - /* Generate a random unsigned char integer */ - buffer[i] = (unsigned char)(rand() - RAND_MAX / 2) >> 4; - - //printf("buffer[%d] = %d\n", i, buffer[i]); - } -} - - -osaudio_result_t osaudio_open(osaudio_t* audio, osaudio_config_t* config) -{ - osaudio_result_t result; - unsigned short dmaMaskRegister; - unsigned short flipflopRegister; - unsigned char dmaChannel; - unsigned int maxDMABufferSizeInFrames; - unsigned int actualDMABufferSizeInFrames; - unsigned int actualDMABufferSizeInBytes; - void far* pDMABuffer; - unsigned char picMask; - - - if (audio == NULL) { - return OSAUDIO_INVALID_ARGS; - } - - *audio = NULL; - - if (config == NULL) { - return OSAUDIO_INVALID_ARGS; - } - - /* We can only have a single device open at a time. */ - if (osaudio_g_audio != NULL) { - return OSAUDIO_ERROR; - } - - /* First check that we have SB16. */ - result = osaudio_init_sb16(); - if (result != OSAUDIO_SUCCESS) { - return result; - } - - /* Capture mode is not supported on anything older than Sound Blaster 16. */ - if (config->direction == OSAUDIO_INPUT && osaudio_g_sb16_version_major < 4) { - return OSAUDIO_ERROR; /* Capture mode not supported. */ - } - - /* We're going to choose our native format configuration first. This way we can determine the Sound Blaster ports, channels and the size of the DMA buffer. */ - if (config->format == OSAUDIO_FORMAT_UNKNOWN || (config->format != OSAUDIO_FORMAT_S16 && config->format != OSAUDIO_FORMAT_U8)) { - config->format = osaudio_g_supportedFormats[0]; - } - - if (config->channels == 0 || config->channels > 2) { - config->channels = osaudio_g_supportedChannels[0]; - } - - if (config->rate == 0) { - config->rate = osaudio_g_supportedSampleRates[0]; - } - - if (config->rate > 44100) { - config->rate = 44100; - } - if (config->rate < 8000) { - config->rate = 8000; - } - - /*config->rate = osaudio_find_closest_rate(config->rate, osaudio_g_supportedSampleRates, OSAUDIO_COUNTOF(osaudio_g_supportedSampleRates));*/ - - /* - Calculate a desired buffer size if none was specified. I'm not quite sure what would be an - appropriate default in milliseconds, but lets go with 20ms for now. The buffer size is in - frames, not bytes. - */ - if (config->buffer_size == 0) { - config->buffer_size = (80 * (unsigned long)config->rate) / 1000; - } - - - /* - Getting here means Sound Blaster 16 is available. We now need to allocate memory. There are two - things needing allocating - the DMA buffer, and the osaudio_t object. The allocation of the - osaudio_t object will use malloc(). - - The allocation of the DMA buffer will use the DOS interupt 0x21, function 0x48 for allocation. - This ensures that it will be allocated within the first 1MB which we need for Sound Blaster. In - addition, by making sure we don't allocate more than 65520 bytes we can ensure we don't cross a - 64KB boundary which is another requirement. - */ - maxDMABufferSizeInFrames = 65520 / config->channels / ((config->format == OSAUDIO_FORMAT_S16) ? 2 : 1); /* 65520 = max size in bytes. */ - - /* - The actual size of the buffer is equal to what we asked for in the configuration. The config - will contain valid values at this point. - */ - actualDMABufferSizeInFrames = config->buffer_size; - if (actualDMABufferSizeInFrames > maxDMABufferSizeInFrames) { - actualDMABufferSizeInFrames = maxDMABufferSizeInFrames; - } - - actualDMABufferSizeInBytes = actualDMABufferSizeInFrames * config->channels * ((config->format == OSAUDIO_FORMAT_S16) ? 2 : 1); - - /* Now that we have the size of the DMA buffer we can allocate it. 2x because we're using double buffering. */ - pDMABuffer = osaudio_dos_calloc(actualDMABufferSizeInBytes * 2); - if (pDMABuffer == NULL) { - return OSAUDIO_OUT_OF_MEMORY; - } - - /*printf("actualDMABufferSizeInFrames = %d; maxDMABufferSizeInFrames = %d;\n", actualDMABufferSizeInFrames, maxDMABufferSizeInFrames);*/ - - /* - if (config->format == OSAUDIO_FORMAT_S16) { - fill_random_unsigned_chars(pDMABuffer, actualDMABufferSizeInFrames * config->channels * 2 * 2); - } else { - fill_random_unsigned_chars(pDMABuffer, actualDMABufferSizeInFrames * config->channels * 2); - } - */ - - //printf("actualDMABufferSizeInBytes = %u\n", actualDMABufferSizeInBytes); - - /* - Allocate the osaudio_t object using malloc(). This is convenient because it means we can use a - near pointer for this object since that is how it's declared in osaudio.h. - */ - *audio = (osaudio_t)calloc(1, sizeof(**audio)); - if (*audio == NULL) { - osaudio_dos_free(pDMABuffer); - return OSAUDIO_OUT_OF_MEMORY; - } - - (*audio)->subBufferIndex = 0; - - /* For playback we want to start our sub-buffer at 1. */ - if (config->direction == OSAUDIO_OUTPUT) { - (*audio)->subBufferIndex = 1; - } else { - (*audio)->subBufferIndex = 0; - } - - - /* Turn on speaker. Can this be done last? */ - osaudio_outportb(OSAUDIO_SB_BASE_PORT + OSAUDIO_SB_DSP_WRITE_PORT, 0xD1); - - /* Volume control. */ - /*osaudio_outportb(OSAUDIO_SB_BASE_PORT + OSAUDIO_SB_MIXER_PORT, 0x22);*/ /* 0x22 = Master Volume. */ - /*osaudio_outportb(OSAUDIO_SB_BASE_PORT + OSAUDIO_SB_MIXER_DATA_PORT, 0xCC);*/ /* 0xLR */ - - - /* Set up our interrupt. This is where we'll be notified when the buffer can be updated. */ - (*audio)->old_isr = _dos_getvect(OSAUDIO_SB_IRQ + 8); - _dos_setvect(OSAUDIO_SB_IRQ + 8, osaudio_isa_dma_interrupt_handler); - - - /* Need to unmask the interrupt or else nothing will be heard. Should probably do the opposite of this when we're done with the device. */ - picMask = osaudio_inportb(0x21); - picMask = picMask & ~(1 << OSAUDIO_SB_IRQ); - osaudio_outportb(0x21, picMask); - - - /* At this point we've allocated our memory. We can now configure the DMA. */ - - /* First we need to determine the mask register and the DMA channel. */ - if (config->format == OSAUDIO_FORMAT_S16) { - dmaMaskRegister = OSAUDIO_ISA_DMA_MASK_REGISTER_16BIT; - flipflopRegister = OSAUDIO_ISA_DMA_FLIPFLOP_REGISTER_16BIT; - dmaChannel = OSAUDIO_SB_DMA_CHANNEL_16; - } else { - dmaMaskRegister = OSAUDIO_ISA_DMA_MASK_REGISTER_8BIT; - flipflopRegister = OSAUDIO_ISA_DMA_FLIPFLOP_REGISTER_8BIT; - dmaChannel = OSAUDIO_SB_DMA_CHANNEL_8; - } - - /*printf("DMA Channel: %d\n", dmaChannel);*/ - - /* Now we can mask the channel. */ - osaudio_outportb(dmaMaskRegister, (dmaChannel & 0x03) | 0x04); - { - osaudio_outportb(flipflopRegister, 0xFF); - - /* Mode. */ - { - unsigned char modeRegister = (config->format == OSAUDIO_FORMAT_S16) ? OSAUDIO_ISA_DMA_MODE_REGISTER_16BIT : OSAUDIO_ISA_DMA_MODE_REGISTER_8BIT; - unsigned char mode = OSAUDIO_ISA_DMA_MODE_DEMAND | OSAUDIO_ISA_DMA_MODE_AUTOINIT; - - if (config->direction == OSAUDIO_OUTPUT) { - mode |= OSAUDIO_ISA_DMA_MODE_READ; /* From the perspective of the device. It's reading from the DMA buffer. */ - } else { - mode |= OSAUDIO_ISA_DMA_MODE_WRITE; /* From the perspective of the device. It's writing to the DMA buffer. */ - } - - mode |= (dmaChannel & 0x03); /* The DMA channel. */ - - /* print the mode as a hex value for testing. */ - /*printf("Mode: 0x%02x\n", mode);*/ - - osaudio_outportb(modeRegister, mode); - } - - /* Address. */ - { - unsigned char addressRegister; - unsigned char pageRegister; - unsigned long address; - - if (config->format == OSAUDIO_FORMAT_S16) { - addressRegister = OSAUDIO_ISA_DMA_ADDRESS_REGISTER_16BIT + ((dmaChannel & 0x03) * 4); - } else { - addressRegister = OSAUDIO_ISA_DMA_ADDRESS_REGISTER_8BIT + ((dmaChannel & 0x03) * 2); - } - - /* The page register is annoying. It's different depending on the DMA channel. */ - switch (dmaChannel) { - case 1: pageRegister = 0x83; break; - case 2: pageRegister = 0x81; break; - case 3: pageRegister = 0x82; break; - case 5: pageRegister = 0x8B; break; - case 6: pageRegister = 0x89; break; - case 7: pageRegister = 0x8A; break; - default: pageRegister = 0; break; /* Will never get here. */ - } - - address = ((unsigned long)FP_SEG(pDMABuffer) << 4) + FP_OFF(pDMABuffer); - - /* - Need to do a random shift by 1 bit when specifying the address in 16-bit mode. This one - screwed me over hardcore. Thanks to OSDev for the tip: - - https://web.archive.org/web/20230731120125/https://wiki.osdev.org/ISA_DMA#16_bit_issues - */ - if (config->format == OSAUDIO_FORMAT_S16) { - address >>= 1; - } - - /* print the address as a hex value for testing. */ - /* - printf("Address: 0x%04x\n", address); - printf("Address Register: 0x%02x\n", addressRegister); - printf("Page Register: 0x%02x\n", pageRegister); - */ - - /* Page. */ - osaudio_outportb(pageRegister, (address >> 16) & 0xFF); - - /* Address. */ - osaudio_outportb(addressRegister, (address >> 0) & 0xFF); - osaudio_outportb(addressRegister, (address >> 8) & 0xFF); - } - - /* Size */ - { - unsigned char countRegister; - unsigned int count; - - if (config->format == OSAUDIO_FORMAT_S16) { - countRegister = OSAUDIO_ISA_DMA_COUNT_REGISTER_16BIT + ((dmaChannel & 0x03) * 4); - } else { - countRegister = OSAUDIO_ISA_DMA_COUNT_REGISTER_8BIT + ((dmaChannel & 0x03) * 2); - } - - /* - We're using a double-buffering technique which means we want to double the size of the buffer. - */ - count = actualDMABufferSizeInBytes * 2; /* 2x because of double buffering. */ - if (config->format == OSAUDIO_FORMAT_S16) { - count >>= 1; /* 1/2 because of 16-bit mode. */ - } - - count -= 1; - - /* - printf("Size: 0x%04x\n", count); - printf("Count Register: 0x%02x\n", countRegister); - */ - - /* Size. */ - osaudio_outportb(countRegister, (count >> 0) & 0xFF); - osaudio_outportb(countRegister, (count >> 8) & 0xFF); - } - } - osaudio_outportb(dmaMaskRegister, (dmaChannel & 0x03)); - - /* At this point the DMA buffer should be configured. We can now configure the DSP. */ - - /* Sample Rate */ - { - unsigned char command; - unsigned short sampleRate; /* Or time constant. */ - - /* For Sound Blaster 16 we'll use the exact sample rate. Otherwise we'll use the time constant. */ - if (osaudio_g_sb16_version_major == 4) { - if (config->direction == OSAUDIO_OUTPUT) { - command = 0x41; - } else { - command = 0x42; - } - - sampleRate = config->rate; - } else { - command = 0x40; - sampleRate = 65536 - ((unsigned long)256000000 / (config->channels * config->rate)); - } - - /*printf("Command: 0x%02x\n", command);*/ - /*printf("Sample Rate: %u\n", sampleRate);*/ - - osaudio_outportb(OSAUDIO_SB_BASE_PORT + OSAUDIO_SB_DSP_WRITE_PORT, command); - - if (osaudio_g_sb16_version_major == 4) { - /* Note that it's high byte first, unlike the block size below which is low byte first. This one bit me. */ - osaudio_outportb(OSAUDIO_SB_BASE_PORT + OSAUDIO_SB_DSP_WRITE_PORT, (sampleRate >> 8) & 0xFF); - osaudio_outportb(OSAUDIO_SB_BASE_PORT + OSAUDIO_SB_DSP_WRITE_PORT, (sampleRate >> 0) & 0xFF); - } else { - osaudio_outportb(OSAUDIO_SB_BASE_PORT + OSAUDIO_SB_DSP_WRITE_PORT, (sampleRate >> 8) & 0xFF); - } - } - - /* Data Format and Block Size. */ - { - unsigned char command; - unsigned char mode; - unsigned int blockSize; /* In bytes. */ - - if (config->format == OSAUDIO_FORMAT_S16) { - if (config->direction == OSAUDIO_OUTPUT) { - command = 0xB6; - } else { - command = 0xBE; - } - - if (config->channels == 1) { - mode = 0x10; - } else { - mode = 0x30; - } - } else { - if (config->direction == OSAUDIO_OUTPUT) { - command = 0xC6; - } else { - command = 0xCE; - } - - if (config->channels == 1) { - mode = 0x00; - } else { - mode = 0x20; - } - } - - blockSize = actualDMABufferSizeInBytes; - if (config->format == OSAUDIO_FORMAT_S16) { - blockSize >>= 1; /* 1/2 because of 16-bit mode. */ - } - - blockSize -= 1; /* Needs to be one less than the actual size. */ - - /* - printf("Command: 0x%02x\n", command); - printf("Mode: 0x%02x\n", mode); - printf("Block Size: %u\n", blockSize); - */ - - osaudio_outportb(OSAUDIO_SB_BASE_PORT + OSAUDIO_SB_DSP_WRITE_PORT, command); - osaudio_outportb(OSAUDIO_SB_BASE_PORT + OSAUDIO_SB_DSP_WRITE_PORT, mode); - osaudio_outportb(OSAUDIO_SB_BASE_PORT + OSAUDIO_SB_DSP_WRITE_PORT, (blockSize >> 0) & 0xFF); - osaudio_outportb(OSAUDIO_SB_BASE_PORT + OSAUDIO_SB_DSP_WRITE_PORT, (blockSize >> 8) & 0xFF); - } - - /* Start in a paused state. */ - if (config->format == OSAUDIO_FORMAT_S16) { - osaudio_outportb(OSAUDIO_SB_BASE_PORT + OSAUDIO_SB_DSP_WRITE_PORT, 0xD5); - } else { - osaudio_outportb(OSAUDIO_SB_BASE_PORT + OSAUDIO_SB_DSP_WRITE_PORT, 0xD0); - } - - - (*audio)->pDMABuffer = pDMABuffer; - strcpy((*audio)->info.name, OSAUDIO_SB_DEVICE_NAME); - (*audio)->info.direction = config->direction; - (*audio)->info.config_count = 1; - (*audio)->info.configs = &(*audio)->config; - (*audio)->config = *config; - (*audio)->config.buffer_size = actualDMABufferSizeInFrames; - - /* Don't forget to set the global audio object. We need this for the ISR. */ - osaudio_g_audio = *audio; - - return OSAUDIO_SUCCESS; -} - -osaudio_result_t osaudio_close(osaudio_t audio) -{ - if (audio == NULL) { - return OSAUDIO_INVALID_ARGS; - } - - /* TODO: Implement me. Look at the SB docs for how to properly shut down. */ - - /* Restore the old ISR. */ - _dos_setvect(OSAUDIO_SB_IRQ + 8, audio->old_isr); - - /* Free the DMA buffer. */ - osaudio_dos_free(audio->pDMABuffer); - - /* Now we can free the osaudio_t object. */ - free(audio); - - return OSAUDIO_SUCCESS; -} - -static void osaudio_activate(osaudio_t audio) -{ - if (audio->config.format == OSAUDIO_FORMAT_S16) { - osaudio_outportb(OSAUDIO_SB_BASE_PORT + OSAUDIO_SB_DSP_WRITE_PORT, 0xD6); - } else { - osaudio_outportb(OSAUDIO_SB_BASE_PORT + OSAUDIO_SB_DSP_WRITE_PORT, 0xD4); - } - - audio->isActive = 1; -} - -osaudio_result_t osaudio_write(osaudio_t audio, const void OSAUDIO_FAR* data, unsigned int frame_count) -{ - if (audio == NULL) { - return OSAUDIO_INVALID_ARGS; - } - - //printf("ENTERED: frame_count = %u\n", frame_count); - while (frame_count > 0) { - unsigned int framesAvailable; - - if (audio->config.buffer_size > audio->cursor) { - framesAvailable = audio->config.buffer_size - audio->cursor; - } else { - framesAvailable = 0; - } - - if (framesAvailable > 0) { - void far* dst; - unsigned int bytesPerFrame; - unsigned int framesToWrite = framesAvailable; - if (framesToWrite > frame_count) { - framesToWrite = frame_count; - } - - //printf("framesAvailable = %u - %u = %u\n", audio->config.buffer_size, audio->cursor, framesAvailable); - /*printf("framesToWrite = %u; framesAvailable = %u\n", framesToWrite, framesAvailable);*/ - - /* - Now we can copy the data. We're doing a cheeky little optimization here. If the input - data pointer is equal to the DMA destination, we can skip the copy. This might happen - when the caller is writing directly to the DMA buffer, which they may be doing by using - a self-managed DMA buffer, in combination with osaudio_get_avail(). - */ - bytesPerFrame = audio->config.channels * (audio->config.format == OSAUDIO_FORMAT_S16 ? 2 : 1); - - dst = (void far*)((char far*)audio->pDMABuffer + (audio->subBufferIndex * audio->config.buffer_size * bytesPerFrame) + (audio->cursor * bytesPerFrame)); - if (dst == data) { - /* Don't do anything. */ - } else { - /* Move the memory. */ - unsigned int i; - for (i = 0; i < framesToWrite * audio->config.channels; i += 1) { - if (audio->config.format == OSAUDIO_FORMAT_S16) { - ((short far*)dst)[i] = ((short far*)data)[i]; - } else { - ((char far*)dst)[i] = ((char far*)data)[i]; - } - } - } - - /* Update the cursor. */ - audio->cursor += framesToWrite; - frame_count -= framesToWrite; - data = (char far*)data + (framesToWrite * bytesPerFrame); - - /* Activate the device. */ - if (!audio->isActive) { - osaudio_activate(audio); - } - } else { - /* No room. */ - if (!audio->isActive) { - break; - } else { - /* Just keep looping. Don't sleep here - in my testing there just isn't enough resolution in the sleep timer. */ - } - } - } - - return OSAUDIO_SUCCESS; -} - -osaudio_result_t osaudio_read(osaudio_t audio, void OSAUDIO_FAR* data, unsigned int frame_count) -{ - if (audio == NULL) { - return OSAUDIO_INVALID_ARGS; - } - - /* TODO: Implement me. */ - - return OSAUDIO_SUCCESS; -} - -osaudio_result_t osaudio_drain(osaudio_t audio) -{ - if (audio == NULL) { - return OSAUDIO_INVALID_ARGS; - } - - /* - It's an invalid operation to drain while the device is paused or else we'll never return from - this function. - */ - if (audio->isPaused) { - return OSAUDIO_INVALID_OPERATION; - } - - /* DOS is single threaded so we don't need to worry about waiting for any pending reads or writes. */ - - - - /* TODO: Implement me. */ - - return OSAUDIO_SUCCESS; -} - -osaudio_result_t osaudio_flush(osaudio_t audio) -{ - if (audio == NULL) { - return OSAUDIO_INVALID_ARGS; - } - - /* TODO: Implement me. */ - - return OSAUDIO_SUCCESS; -} - -osaudio_result_t osaudio_pause(osaudio_t audio) -{ - if (audio == NULL || audio != osaudio_g_audio) { - return OSAUDIO_INVALID_ARGS; - } - - if (audio->isPaused) { - return OSAUDIO_SUCCESS; - } - - /* No need to deactivate the device if it's already inactive. */ - if (audio->isActive) { - if (audio->config.format == OSAUDIO_FORMAT_S16) { - osaudio_outportb(OSAUDIO_SB_BASE_PORT + OSAUDIO_SB_DSP_WRITE_PORT, 0xD5); - } else { - osaudio_outportb(OSAUDIO_SB_BASE_PORT + OSAUDIO_SB_DSP_WRITE_PORT, 0xD0); - } - } - - audio->isPaused = 1; - - return OSAUDIO_SUCCESS; -} - -osaudio_result_t osaudio_resume(osaudio_t audio) -{ - if (audio == NULL || audio != osaudio_g_audio) { - return OSAUDIO_INVALID_ARGS; - } - - if (!audio->isPaused) { - return OSAUDIO_SUCCESS; - } - - /* Do not activate the device if it's inactive. */ - if (audio->isActive) { - osaudio_activate(audio); - } - - audio->isPaused = 0; - - return OSAUDIO_SUCCESS; -} - -unsigned int osaudio_get_avail(osaudio_t audio) -{ - /* TODO: Implement me. */ - - return 0; -} - -const osaudio_info_t* osaudio_get_info(osaudio_t audio) -{ - if (audio == NULL) { - return NULL; - } - - return &audio->info; -} - -#endif /* osaudio_dos_soundblaster_c */ -#endif /* __MSDOS__ || __DOS__ */ diff --git a/extras/osaudio/osaudio_miniaudio.c b/extras/osaudio/osaudio_miniaudio.c deleted file mode 100644 index a3ebdd68..00000000 --- a/extras/osaudio/osaudio_miniaudio.c +++ /dev/null @@ -1,948 +0,0 @@ -/* -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 */ diff --git a/extras/osaudio/tests/osaudio_deviceio.c b/extras/osaudio/tests/osaudio_deviceio.c deleted file mode 100644 index a08aaf05..00000000 --- a/extras/osaudio/tests/osaudio_deviceio.c +++ /dev/null @@ -1,196 +0,0 @@ -#include "../osaudio.h" - -/* This example uses miniaudio for decoding audio files. */ -#define MINIAUDIO_IMPLEMENTATION -#include "../../../miniaudio.h" - -#include -#include -#include - -#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; -} \ No newline at end of file diff --git a/extras/osaudio/tests/osaudio_sine.c b/extras/osaudio/tests/osaudio_sine.c deleted file mode 100644 index 4619e868..00000000 --- a/extras/osaudio/tests/osaudio_sine.c +++ /dev/null @@ -1,283 +0,0 @@ -#include "../osaudio.h" -#include "../../decoders/litewav/litewav.c" - -#include -#include /* free() */ - -#if defined(__MSDOS__) || defined(__DOS__) - #include - #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 - -/* Sine wave generation. */ -#include - -#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; -}