1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250
|
/*
This example demonstrates some of the advanced features of the high level engine API.
The following features are demonstrated:
* Initialization of the engine from a pre-initialized device.
* Self-managed resource managers.
* Multiple engines with a shared resource manager.
* Creation and management of `ma_sound` objects.
This example will play the sound that's passed in on the command line.
Using a shared resource manager, as we do in this example, is useful for when you want to user
multiple engines so that you can output to multiple playback devices simultaneoulys. An example
might be a local co-op multiplayer game where each player has their own headphones.
*/
#include "../miniaudio.c"
#define MAX_DEVICES 2
#define MAX_SOUNDS 32
void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
{
(void)pInput;
/*
Since we're managing the underlying device ourselves, we need to read from the engine directly.
To do this we need access to the `ma_engine` object which we passed in to the user data. One
advantage of this is that you could do your own audio processing in addition to the engine's
standard processing.
*/
ma_engine_read_pcm_frames((ma_engine*)pDevice->pUserData, pOutput, frameCount, NULL);
}
int main(int argc, char** argv)
{
ma_result result;
ma_context context;
ma_resource_manager_config resourceManagerConfig;
ma_resource_manager resourceManager;
ma_engine engines[MAX_DEVICES];
ma_device devices[MAX_DEVICES];
ma_uint32 engineCount = 0;
ma_uint32 iEngine;
ma_device_info* pPlaybackDeviceInfos;
ma_uint32 playbackDeviceCount;
ma_uint32 iAvailableDevice;
ma_uint32 iChosenDevice;
ma_sound sounds[MAX_SOUNDS];
ma_uint32 soundCount;
ma_uint32 iSound;
if (argc < 2) {
printf("No input file.");
return -1;
}
/*
We are going to be initializing multiple engines. In order to save on memory usage we can use a self managed
resource manager so we can share a single resource manager across multiple engines.
*/
resourceManagerConfig = ma_resource_manager_config_init();
resourceManagerConfig.decodedFormat = ma_format_f32; /* ma_format_f32 should almost always be used as that's what the engine (and most everything else) uses for mixing. */
resourceManagerConfig.decodedChannels = 0; /* Setting the channel count to 0 will cause sounds to use their native channel count. */
resourceManagerConfig.decodedSampleRate = 48000; /* Using a consistent sample rate is useful for avoiding expensive resampling in the audio thread. This will result in resampling being performed by the loading thread(s). */
result = ma_resource_manager_init(&resourceManagerConfig, &resourceManager);
if (result != MA_SUCCESS) {
printf("Failed to initialize resource manager.");
return -1;
}
/* We're going to want a context so we can enumerate our playback devices. */
result = ma_context_init(NULL, 0, NULL, &context);
if (result != MA_SUCCESS) {
printf("Failed to initialize context.");
return -1;
}
/*
Now that we have a context we will want to enumerate over each device so we can display them to the user and give
them a chance to select the output devices they want to use.
*/
result = ma_context_get_devices(&context, &pPlaybackDeviceInfos, &playbackDeviceCount, NULL, NULL);
if (result != MA_SUCCESS) {
printf("Failed to enumerate playback devices.");
ma_context_uninit(&context);
return -1;
}
/* We have our devices, so now we want to get the user to select the devices they want to output to. */
engineCount = 0;
for (iChosenDevice = 0; iChosenDevice < MAX_DEVICES; iChosenDevice += 1) {
int c = 0;
for (;;) {
printf("Select playback device %d ([%d - %d], Q to quit):\n", iChosenDevice+1, 0, ma_min((int)playbackDeviceCount, 9));
for (iAvailableDevice = 0; iAvailableDevice < playbackDeviceCount; iAvailableDevice += 1) {
printf(" %d: %s\n", iAvailableDevice, pPlaybackDeviceInfos[iAvailableDevice].name);
}
for (;;) {
c = getchar();
if (c != '\n') {
break;
}
}
if (c == 'q' || c == 'Q') {
return 0; /* User aborted. */
}
if (c >= '0' && c <= '9') {
c -= '0';
if (c < (int)playbackDeviceCount) {
ma_device_config deviceConfig;
ma_engine_config engineConfig;
/*
Create the device first before the engine. We'll specify the device in the engine's config. This is optional. When a device is
not pre-initialized the engine will create one for you internally. The device does not need to be started here - the engine will
do that for us in `ma_engine_start()`. The device's format is derived from the resource manager, but can be whatever you want.
It's useful to keep the format consistent with the resource manager to avoid data conversions costs in the audio callback. In
this example we're using the resource manager's sample format and sample rate, but leaving the channel count set to the device's
native channels. You can use whatever format/channels/rate you like.
*/
deviceConfig = ma_device_config_init(ma_device_type_playback);
deviceConfig.playback.pDeviceID = &pPlaybackDeviceInfos[c].id;
deviceConfig.playback.format = resourceManager.config.decodedFormat;
deviceConfig.playback.channels = 0;
deviceConfig.sampleRate = resourceManager.config.decodedSampleRate;
deviceConfig.dataCallback = data_callback;
deviceConfig.pUserData = &engines[engineCount];
result = ma_device_init(&context, &deviceConfig, &devices[engineCount]);
if (result != MA_SUCCESS) {
printf("Failed to initialize device for %s.\n", pPlaybackDeviceInfos[c].name);
return -1;
}
/* Now that we have the device we can initialize the engine. The device is passed into the engine's config. */
engineConfig = ma_engine_config_init();
engineConfig.pDevice = &devices[engineCount];
engineConfig.pResourceManager = &resourceManager;
engineConfig.noAutoStart = MA_TRUE; /* Don't start the engine by default - we'll do that manually below. */
result = ma_engine_init(&engineConfig, &engines[engineCount]);
if (result != MA_SUCCESS) {
printf("Failed to initialize engine for %s.\n", pPlaybackDeviceInfos[c].name);
ma_device_uninit(&devices[engineCount]);
return -1;
}
engineCount += 1;
break;
} else {
printf("Invalid device number.\n");
}
} else {
printf("Invalid device number.\n");
}
}
printf("Device %d: %s\n", iChosenDevice+1, pPlaybackDeviceInfos[c].name);
}
/* We should now have our engine's initialized. We can now start them. */
for (iEngine = 0; iEngine < engineCount; iEngine += 1) {
result = ma_engine_start(&engines[iEngine]);
if (result != MA_SUCCESS) {
printf("WARNING: Failed to start engine %d.\n", iEngine);
}
}
/*
At this point our engine's are running and outputting nothing but silence. To get them playing something we'll need
some sounds. In this example we're just using one sound per engine, but you can create as many as you like. Since
we're using a shared resource manager, the sound data will only be loaded once. This is how you would implement
multiple listeners.
*/
soundCount = 0;
for (iEngine = 0; iEngine < engineCount; iEngine += 1) {
/* Just one sound per engine in this example. We're going to be loading this asynchronously. */
result = ma_sound_init_from_file(&engines[iEngine], argv[1], MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE | MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC | MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM, NULL, NULL, &sounds[iEngine]);
if (result != MA_SUCCESS) {
printf("WARNING: Failed to load sound \"%s\"", argv[1]);
break;
}
/*
The sound can be started as soon as ma_sound_init_from_file() returns, even for sounds that are initialized
with MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC. The sound will start playing while it's being loaded. Note that if the
asynchronous loading process cannot keep up with the rate at which you try reading you'll end up glitching.
If this is an issue, you need to not load sounds asynchronously.
*/
result = ma_sound_start(&sounds[iEngine]);
if (result != MA_SUCCESS) {
printf("WARNING: Failed to start sound.");
}
soundCount += 1;
}
printf("Press Enter to quit...");
getchar();
for (;;) {
int c = getchar();
if (c == '\n') {
break;
}
}
/* Teardown. */
/* The application owns the `ma_sound` object which means you're responsible for uninitializing them. */
for (iSound = 0; iSound < soundCount; iSound += 1) {
ma_sound_uninit(&sounds[iSound]);
}
/* We can now uninitialize each engine. */
for (iEngine = 0; iEngine < engineCount; iEngine += 1) {
ma_engine_uninit(&engines[iEngine]);
/*
The engine has been uninitialized so now lets uninitialize the device. Do this first to ensure we don't
uninitialize the resource manager from under the device while the data callback is running.
*/
ma_device_uninit(&devices[iEngine]);
}
/* The context can only be uninitialized after the devices. */
ma_context_uninit(&context);
/*
Do the resource manager last. This way we can guarantee the data callbacks of each device aren't trying to access
and data managed by the resource manager.
*/
ma_resource_manager_uninit(&resourceManager);
return 0;
}
|