diff --git a/.gitignore b/.gitignore index 2467e6a7..b2c31501 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,12 @@ /tests/dreamcast/romdisk.img /tests/dreamcast/romdisk.o /tests/dreamcast/romdisk/test.mp3 +/tests/xbox/bin/ +/tests/xbox/*.d +/tests/xbox/*.obj +/tests/xbox/*.iso +/tests/xbox/*.exe +/tests/xbox/test.mp3 /tests/*.c /tests/*.cpp /website/docs/ diff --git a/README.md b/README.md index bca01648..33cc4bf3 100644 --- a/README.md +++ b/README.md @@ -180,6 +180,7 @@ Supported Platforms - Raspberry Pi - Emscripten / HTML5 - Dreamcast (via KallistiOS) +- Original Xbox (via NXDK) miniaudio should compile clean on other platforms, but it will not include any support for playback or capture by default. To support that, you would need to implement a custom backend. You can do this without needing to @@ -202,6 +203,7 @@ Backends - OpenSL|ES (Android only) - Web Audio (Emscripten) - Dreamcast (via KallistiOS) +- XAudio (Original Xbox via NXDK) - Null (Silence) - Custom diff --git a/miniaudio.h b/miniaudio.h index 0294e753..6d5711c9 100644 --- a/miniaudio.h +++ b/miniaudio.h @@ -3739,6 +3739,7 @@ example, ALSA, which is specific to Linux, will not be included in the Windows b | OpenSL|ES | ma_device_backend_opensl | Android (API level 16+) | | Web Audio | ma_device_backend_webaudio | Web (via Emscripten) | | Dreamcast | ma_device_backend_dreamcast | KallistiOS | + | XAudio | ma_device_backend_xaudio | NXDK | | Null | ma_device_backend_null | Cross Platform (not used on Web) | +-------------+------------------------------+--------------------------------------------------------+ @@ -3862,6 +3863,26 @@ disabling threading at compile time you will also reduce memory usage of the `ma When running in single threaded mode you need to manually step the device periodically with `ma_device_step()`, which you would normally do each frame. +15.7. XAudio (Original Xbox) +---------------------------- +The XAudio backend is used for the original Xbox and uses NXDK. + +You should only initialize a single `ma_device` object. Do not create more than one device thinking +that mixing will be done in hardware. Do your mixing in software. + +The following device configuration is optimal: + + Format: ma_foramt_s16 + Channels: 2 + Sample Rate: 48000 + Period Size: 1024 to 16383 + noFixedSizedCallback: True + +If you follow this configuration you can be guaranteed an optimized passthrough pipeline. You can +use a period size smaller than 1024, but in my testing I found that it can result in glitches. You +are not forced to use this configuration, but if you deviate from it miniaudio will need to do data +conversion. + 16. Optimization Tips ===================== @@ -7426,6 +7447,28 @@ MA_API ma_device_backend_vtable* ma_dreamcast_get_vtable(void); /* END miniaudio_dreamcast.h */ +/* BEG miniaudio_xaudio.h */ +extern ma_device_backend_vtable* ma_device_backend_xaudio; +MA_API ma_device_backend_vtable* ma_xaudio_get_vtable(void); + + +typedef struct +{ + int _unused; +} ma_context_config_xaudio; + +MA_API ma_context_config_xaudio ma_context_config_xaudio_init(void); + + +typedef struct +{ + int _unused; +} ma_device_config_xaudio; + +MA_API ma_device_config_xaudio ma_device_config_xaudio_init(void); +/* END miniaudio_xaudio.h */ + + /* BEG miniaudio_null.h */ typedef struct ma_context_config_null { @@ -7857,6 +7900,7 @@ struct ma_device_config ma_device_config_opensl opensl; ma_device_config_webaudio webaudio; ma_device_config_dreamcast dreamcast; + ma_device_config_xaudio xaudio; ma_device_config_null null_backend; }; @@ -7997,6 +8041,7 @@ struct ma_context_config ma_context_config_opensl opensl; ma_context_config_webaudio webaudio; ma_context_config_dreamcast dreamcast; + ma_context_config_xaudio xaudio; ma_context_config_null null_backend; }; @@ -19974,6 +20019,9 @@ BACKENDS #if defined(MA_DREAMCAST) #define MA_SUPPORT_DREAMCAST #endif +#if defined(MA_XBOX) + #define MA_SUPPORT_XAUDIO +#endif /* All platforms should support custom backends. */ #define MA_SUPPORT_CUSTOM @@ -20029,6 +20077,9 @@ BACKENDS #if defined(MA_SUPPORT_DREAMCAST) && !defined(MA_NO_DREAMCAST) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_DREAMCAST)) #define MA_HAS_DREAMCAST #endif +#if defined(MA_SUPPORT_XAUDIO) && !defined(MA_NO_XAUDIO) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_XAUDIO)) + #define MA_HAS_XAUDIO +#endif #if defined(MA_SUPPORT_NULL) && !defined(MA_NO_NULL) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_NULL)) #define MA_HAS_NULL #endif @@ -47980,6 +48031,411 @@ MA_API ma_device_config_dreamcast ma_device_config_dreamcast_init(int voiceChann /* END miniaudio_dreamcast.c */ +/* BEG miniaudio_dreamcast.c */ +#if defined(MA_HAS_XAUDIO) +#include +#include + +typedef struct ma_context_state_xaudio +{ + int _unused; +} ma_context_state_xaudio; + +typedef struct ma_device_state_xaudio +{ + void* pBuffer; + ma_uint16 subBufferIndex; + ma_uint16 subBufferCount; + ma_uint16 emptyBufferCount; /* How many sub-buffers are available for filling. */ + ma_spinlock lock; /* Using a dumb lock for thread-safety for now, but this can be optimized by having subBufferIndex and emptyBufferCount encoded into a single 32-bit value and using it atomically. */ + ma_timer timer; + double queuedAudioTime; + double periodTimeInSeconds; +} ma_device_state_xaudio; + +static ma_context_state_xaudio* ma_context_get_backend_state__xaudio(ma_context* pContext) +{ + return (ma_context_state_xaudio*)ma_context_get_backend_state(pContext); +} + +static ma_device_state_xaudio* ma_device_get_backend_state__xaudio(ma_device* pDevice) +{ + return (ma_device_state_xaudio*)ma_device_get_backend_state(pDevice); +} + + +static void ma_backend_info__xaudio(ma_device_backend_info* pBackendInfo) +{ + pBackendInfo->pName = "XAudio"; +} + +static ma_result ma_context_init__xaudio(ma_context* pContext, const void* pContextBackendConfig, void** ppContextState) +{ + ma_context_state_xaudio* pContextStateXAudio; + const ma_context_config_xaudio* pContextConfigXAudio = (ma_context_config_xaudio*)pContextBackendConfig; + + /* The context config is not currently being used for this backend. */ + (void)pContextConfigXAudio; + + #if 0 + { + pContextStateXAudio = (ma_context_state_xaudio*)ma_calloc(sizeof(*pContextStateXAudio), ma_context_get_allocation_callbacks(pContext)); + if (pContextStateXAudio == NULL) { + return MA_OUT_OF_MEMORY; + } + } + #else + { + (void)pContextStateXAudio; + } + #endif + + return MA_SUCCESS; +} + +static void ma_context_uninit__xaudio(ma_context* pContext) +{ + ma_context_state_xaudio* pContextStateXAudio = ma_context_get_backend_state__xaudio(pContext); + + #if 0 + { + ma_free(pContextStateSDL, ma_context_get_allocation_callbacks(pContext)); + } + #else + { + (void)pContextStateXAudio; + } + #endif +} + +static ma_result ma_context_enumerate_devices__xaudio(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pCallbackUserData) +{ + ma_context_state_xaudio* pContextStateXAudio = ma_context_get_backend_state__xaudio(pContext); + ma_device_info deviceInfo; + + (void)pContextStateXAudio; + + /* We're only outputting a single default playback device. */ + MA_ZERO_OBJECT(&deviceInfo); + deviceInfo.isDefault = MA_TRUE; + deviceInfo.id.custom.i = 0; + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), "Default Playback Device", (size_t)-1); + + /* Audio is always s16, stereo, 48000. */ + ma_device_info_add_native_data_format(&deviceInfo, ma_format_s16, 2, 2, 48000, 48000); + + return MA_SUCCESS; +} + +static void* ma_device_get_sub_buffer__xaudio(ma_device* pDevice, ma_uint32 subBufferIndex) +{ + ma_device_state_xaudio* pDeviceStateXAudio = ma_device_get_backend_state__xaudio(pDevice); + return ma_offset_ptr(pDeviceStateXAudio->pBuffer, pDevice->playback.internalPeriodSizeInFrames * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels) * subBufferIndex); +} + +#if 0 +static void ma_device_callback__xaudio(void* pUnused, void* pUserData) +{ + ma_device* pDevice = (ma_device*)pUserData; + ma_device_state_xaudio* pDeviceStateXAudio = ma_device_get_backend_state__xaudio(pDevice); + ma_uint32 bpf = ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); + + (void)pUnused; + + /* + I'm not sure how to handle the situation where we don't have any audio data to submit. Can I just + not call XAudioProvideSamples(), or should I submit a chunk of silence? What's making me question + this is that during initialization of the audio system, at no point are we configuring a period + size so how does the audio system know when the fire this callback? If I don't submit anything, + how frequently does this callback get fired to request more data? + */ + ma_spinlock_lock(&pDeviceStateXAudio->lock); + { + if (pDeviceStateXAudio->emptyBufferCount == 2) { + /* We don't have anything available. Submit silence. */ + ma_int16 pSilence[2048]; + unsigned short silenceFrameCount = (unsigned short)(ma_countof(pSilence) / pDevice->playback.internalChannels); + + MA_ZERO_MEMORY(pSilence, sizeof(pSilence)); + XAudioProvideSamples((void*)pSilence, silenceFrameCount, 0); + } else { + while (pDeviceStateXAudio->emptyBufferCount < pDeviceStateXAudio->subBufferCount) { + XAudioProvideSamples(ma_device_get_sub_buffer__xaudio(pDevice, pDeviceStateXAudio->subBufferIndex), (unsigned short)pDevice->playback.internalPeriodSizeInFrames, 0); + + pDeviceStateXAudio->subBufferIndex = (pDeviceStateXAudio->subBufferIndex + 1) & 1; + pDeviceStateXAudio->emptyBufferCount += 1; + } + } + } + ma_spinlock_unlock(&pDeviceStateXAudio->lock); +} +#endif + +static ma_result ma_device_init__xaudio(ma_device* pDevice, const void* pDeviceBackendConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture, void** ppDeviceState) +{ + ma_device_state_xaudio* pDeviceStateXAudio; + ma_device_config_xaudio* pDeviceConfigXAudio = (ma_device_config_xaudio*)pDeviceBackendConfig; + ma_context_state_xaudio* pContextStateXAudio = ma_context_get_backend_state__xaudio(ma_device_get_context(pDevice)); + ma_device_type deviceType = ma_device_get_type(pDevice); + ma_result result; + int sampleSizeInBits; + ma_format format; + ma_uint32 channels; + ma_uint32 sampleRate; + ma_uint32 bufferSizeInFrames; + + /* Only playback is supported. */ + if (deviceType != ma_device_type_playback) { + return MA_DEVICE_TYPE_NOT_SUPPORTED; + } + + (void)pDescriptorCapture; + + + /* We need to allocate our backend-specific data. */ + pDeviceStateXAudio = (ma_device_state_xaudio*)ma_calloc(sizeof(*pDeviceStateXAudio), ma_device_get_allocation_callbacks(pDevice)); + if (pDeviceStateXAudio == NULL) { + return MA_OUT_OF_MEMORY; + } + + pDeviceStateXAudio->emptyBufferCount = 2; /* Both buffers are empty from the start. */ + ma_timer_init(&pDeviceStateXAudio->timer); + + /* Audio is always s16, stereo, 48000. */ + format = ma_format_s16; + channels = 2; + sampleRate = 48000; + + /* Calculate an appropriate buffer size. */ + bufferSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptorPlayback, sampleRate); + + /* TODO: Do we need to have a minimum buffer size? Need to experiment with this. */ + if (bufferSizeInFrames < 1024) { + bufferSizeInFrames = 1024; + } + + /* + XAudioProvideSamples() takes the size of the buffer in bytes, but that parameter is typed as + an unsigned 16-bit value. We need to make sure the size of the buffer does not exceed this. + */ + if (bufferSizeInFrames > 65535 / ma_get_bytes_per_frame(format, channels)) { + bufferSizeInFrames = 65535 / ma_get_bytes_per_frame(format, channels); + } + + pDeviceStateXAudio->periodTimeInSeconds = bufferSizeInFrames / (double)sampleRate; + pDeviceStateXAudio->subBufferCount = 3; + + /* + Allocate two buffers for double buffering. The nxdk "xaudio" example uses MmAllocateContiguousMemoryEx(), but + I'm not sure if that's strictly required. It'd be nice if instead we could use miniaudio's allocation callbacks + and just allocate this buffer in the same allocation as the state struct, but the example explicitly using + MmAllocateContiguousMemoryEx() makes me feel a bit uneasy about that. Advice welcome. + */ + pDeviceStateXAudio->pBuffer = MmAllocateContiguousMemoryEx(bufferSizeInFrames * ma_get_bytes_per_frame(format, channels) * pDeviceStateXAudio->subBufferCount, 0, 0x03FFAFFF, 0, (PAGE_READWRITE | PAGE_WRITECOMBINE)); + if (pDeviceStateXAudio->pBuffer == NULL) { + ma_free(pDeviceStateXAudio, ma_device_get_allocation_callbacks(pDevice)); + return MA_OUT_OF_MEMORY; + } + + /* + NOTE: + There is a bug in xemu that is resulting in the callback never getting fired. Since I would + like miniaudio to work on xemu I'm going to not use the callback technique. If this is fixed in + the future I may update this. + + With the callback technique `XAudioProvideSamples()` would be called from the callback, but + we're instead going to manually keep track of a timer and call it from our step function. + */ + + /* The audio system must be initialized before it'll be usable. */ + XAudioInit((int)ma_get_bytes_per_sample(format) * 8, (int)channels, NULL, pDevice); + + /* We're done. */ + pDescriptorPlayback->format = format; + pDescriptorPlayback->channels = channels; + pDescriptorPlayback->sampleRate = sampleRate; + pDescriptorPlayback->periodSizeInFrames = bufferSizeInFrames; + pDescriptorPlayback->periodCount = pDeviceStateXAudio->subBufferCount; + + *ppDeviceState = pDeviceStateXAudio; + + return MA_SUCCESS; +} + +static void ma_device_uninit__xaudio(ma_device* pDevice) +{ + ma_device_state_xaudio* pDeviceStateXAudio = ma_device_get_backend_state__xaudio(pDevice); + /*ma_context_state_xaudio* pContextStateXAudio = ma_context_get_backend_state__xaudio(ma_device_get_context(pDevice));*/ + + MmFreeContiguousMemory(pDeviceStateXAudio->pBuffer); + ma_free(pDeviceStateXAudio, ma_device_get_allocation_callbacks(pDevice)); +} + +static void ma_device_fill_sub_buffer__xaudio(ma_device* pDevice, ma_uint32 subBufferIndex) +{ + /* Do the data callback. */ + ma_device_handle_backend_data_callback(pDevice, ma_device_get_sub_buffer__xaudio(pDevice, subBufferIndex), NULL, pDevice->playback.internalPeriodSizeInFrames); +} + +static ma_result ma_device_start__xaudio(ma_device* pDevice) +{ + ma_device_state_xaudio* pDeviceStateXAudio = ma_device_get_backend_state__xaudio(pDevice); + + /* Clear everything out and reload our buffers. We need to submit data to XAudio before we start playing. */ + #if 0 + pDeviceStateXAudio->subBufferIndex = 0; + pDeviceStateXAudio->emptyBufferCount = 0; + ma_device_fill_sub_buffer__xaudio(pDevice, 0); + ma_device_fill_sub_buffer__xaudio(pDevice, 1); + ma_device_callback__xaudio(NULL, pDevice); + #endif + + XAudioPlay(); + pDeviceStateXAudio->queuedAudioTime = ma_timer_get_time_in_seconds(&pDeviceStateXAudio->timer); + + return MA_SUCCESS; +} + +static ma_result ma_device_stop__xaudio(ma_device* pDevice) +{ + ma_device_state_xaudio* pDeviceStateXAudio = ma_device_get_backend_state__xaudio(pDevice); + + (void)pDeviceStateXAudio; + XAudioPause(); + + return MA_SUCCESS; +} + +static ma_result ma_device_step__xaudio(ma_device* pDevice, ma_blocking_mode blockingMode) +{ + ma_device_state_xaudio* pDeviceStateXAudio = ma_device_get_backend_state__xaudio(pDevice); + + /* Keep looping until we have processed some data, or we're in non-blocking mode. */ + for (;;) { + if (!ma_device_is_started(pDevice)) { + return MA_DEVICE_NOT_STARTED; + } + + #if 0 + { + ma_spinlock_lock(&pDeviceStateXAudio->lock); + { + if (pDeviceStateXAudio->emptyBufferCount > 0) { + ma_uint16 iEmptyBuffer; + ma_uint16 subBufferIndex; + + if (pDeviceStateXAudio->emptyBufferCount == 2) { + subBufferIndex = pDeviceStateXAudio->subBufferIndex; + ma_device_fill_sub_buffer__xaudio(pDevice, subBufferIndex); + pDeviceStateXAudio->emptyBufferCount -= 1; + } + + MA_ASSERT(pDeviceStateXAudio->emptyBufferCount == 1); + + subBufferIndex = (pDeviceStateXAudio->subBufferIndex + 1) & ~1; + ma_device_fill_sub_buffer__xaudio(pDevice, subBufferIndex); + pDeviceStateXAudio->emptyBufferCount -= 1; + + ma_spinlock_unlock(&pDeviceStateXAudio->lock); + break; + } + } + ma_spinlock_unlock(&pDeviceStateXAudio->lock); + } + #else + { + double currentTimeInSeconds; + double marginTimeInSeconds; + + currentTimeInSeconds = ma_timer_get_time_in_seconds(&pDeviceStateXAudio->timer); + + /* If we underflow reset the timer to help mitigate getting stuck in a permanent glitched state. */ + if (currentTimeInSeconds > pDeviceStateXAudio->queuedAudioTime) { + pDeviceStateXAudio->queuedAudioTime = currentTimeInSeconds; + } + + /* The margin time is how much audio time we want queued ahead of the current time. */ + marginTimeInSeconds = pDeviceStateXAudio->periodTimeInSeconds * (pDeviceStateXAudio->subBufferCount - 1); + + if (pDeviceStateXAudio->queuedAudioTime - currentTimeInSeconds < marginTimeInSeconds) { + ma_device_fill_sub_buffer__xaudio(pDevice, pDeviceStateXAudio->subBufferIndex); + XAudioProvideSamples(ma_device_get_sub_buffer__xaudio(pDevice, pDeviceStateXAudio->subBufferIndex), (unsigned short)(pDevice->playback.internalPeriodSizeInFrames*ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)), 0); + + //debugPrint("TESTING: %d %d\n", (int)(currentTimeInSeconds * 1000), (int)(pDeviceStateXAudio->queuedAudioTime * 1000)); + pDeviceStateXAudio->subBufferIndex += 1; + if (pDeviceStateXAudio->subBufferIndex == pDeviceStateXAudio->subBufferCount) { + pDeviceStateXAudio->subBufferIndex = 0; + } + + pDeviceStateXAudio->queuedAudioTime += pDeviceStateXAudio->periodTimeInSeconds; + + break; + } + } + #endif + + /* Getting here means there is no data to process. In non blocking mode we just bomb out. In blocking mode we wait a bit. */ + if (blockingMode == MA_BLOCKING_MODE_NON_BLOCKING) { + break; + } + + /* Getting here means we're in blocking mode. */ + ma_sleep(1); + } + + return MA_SUCCESS; +} + +static void ma_device_wakeup__xaudio(ma_device* pDevice) +{ + /* Do nothing. */ + (void)pDevice; +} + +static ma_device_backend_vtable ma_gDeviceBackendVTable_XAudio = +{ + ma_backend_info__xaudio, + ma_context_init__xaudio, + ma_context_uninit__xaudio, + ma_context_enumerate_devices__xaudio, + ma_device_init__xaudio, + ma_device_uninit__xaudio, + ma_device_start__xaudio, + ma_device_stop__xaudio, + ma_device_step__xaudio, + ma_device_wakeup__xaudio +}; + +ma_device_backend_vtable* ma_device_backend_xaudio = &ma_gDeviceBackendVTable_XAudio; +#else +ma_device_backend_vtable* ma_device_backend_xaudio = NULL; +#endif + +MA_API ma_device_backend_vtable* ma_xaudio_get_vtable(void) +{ + return ma_device_backend_xaudio; +} + +MA_API ma_context_config_xaudio ma_context_config_xaudio_init(void) +{ + ma_context_config_xaudio config; + + MA_ZERO_OBJECT(&config); + + return config; +} + +MA_API ma_device_config_xaudio ma_device_config_xaudio_init(void) +{ + ma_device_config_xaudio config; + + MA_ZERO_OBJECT(&config); + + return config; +} +/* END miniaudio_dreamcast.c */ + + MA_API void ma_get_device_backend_info(ma_device_backend_vtable* pBackendVTable, ma_device_backend_info* pBackendInfo) { @@ -49011,6 +49467,9 @@ static const void* ma_context_config_find_backend_config(const ma_context_config if (pVTable == ma_device_backend_dreamcast) { return &pConfig->dreamcast; } + if (pVTable == ma_device_backend_xaudio) { + return &pConfig->xaudio; + } if (pVTable == ma_device_backend_null) { return &pConfig->null_backend; } @@ -49041,6 +49500,7 @@ MA_API ma_uint32 ma_get_stock_device_backends(ma_device_backend_config* pBackend if (backendsCap > count) { pBackends[count++] = ma_device_backend_config_init(ma_device_backend_opensl, NULL); } if (backendsCap > count) { pBackends[count++] = ma_device_backend_config_init(ma_device_backend_webaudio, NULL); } if (backendsCap > count) { pBackends[count++] = ma_device_backend_config_init(ma_device_backend_dreamcast, NULL); } + if (backendsCap > count) { pBackends[count++] = ma_device_backend_config_init(ma_device_backend_xaudio, NULL); } if (backendsCap > count) { pBackends[count++] = ma_device_backend_config_init(ma_device_backend_null, NULL); } return count; @@ -49509,6 +49969,9 @@ static const void* ma_device_config_find_backend_config(const ma_device_config* if (pVTable == ma_device_backend_dreamcast) { return &pConfig->dreamcast; } + if (pVTable == ma_device_backend_xaudio) { + return &pConfig->xaudio; + } if (pVTable == ma_device_backend_null) { return &pConfig->null_backend; } diff --git a/tests/xbox/Makefile b/tests/xbox/Makefile new file mode 100644 index 00000000..da912d9e --- /dev/null +++ b/tests/xbox/Makefile @@ -0,0 +1,13 @@ +XBE_TITLE = miniaudio_xbox +GEN_XISO = $(XBE_TITLE).iso +SRCS = $(CURDIR)/miniaudio_xbox.c +NXDK_DIR ?= /opt/toolchains/nxdk + +all: + +include $(NXDK_DIR)/Makefile + +TARGET += $(OUTPUT_DIR)/test.mp3 +$(GEN_XISO): $(OUTPUT_DIR)/test.mp3 +$(OUTPUT_DIR)/test.mp3: $(CURDIR)/test.mp3 $(OUTPUT_DIR) + $(VE)cp '$<' '$@' diff --git a/tests/xbox/README b/tests/xbox/README new file mode 100644 index 00000000..600f0b99 --- /dev/null +++ b/tests/xbox/README @@ -0,0 +1 @@ +Add a "test.mp3" to this folder. \ No newline at end of file diff --git a/tests/xbox/miniaudio_xbox.c b/tests/xbox/miniaudio_xbox.c new file mode 100644 index 00000000..85294708 --- /dev/null +++ b/tests/xbox/miniaudio_xbox.c @@ -0,0 +1,70 @@ +#include "../../miniaudio.c" + +#include +#include + +static void data_callback(ma_device* pDevice, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount) +{ + ma_data_source* pDataSource = (ma_data_source*)ma_device_get_user_data(pDevice); + ma_data_source_read_pcm_frames(pDataSource, pFramesOut, frameCount, NULL); + + (void)pFramesIn; + (void)pDevice; +} + +int main() +{ + ma_result result; + ma_device_config deviceConfig; + ma_device device; + ma_waveform waveform; + ma_decoder decoder; + + XVideoSetMode(640, 480, 32, REFRESH_DEFAULT); + + deviceConfig = ma_device_config_init(ma_device_type_playback); + /*deviceConfig.threadingMode = MA_THREADING_MODE_SINGLE_THREADED;*/ + deviceConfig.playback.format = ma_format_s16; + deviceConfig.playback.channels = 2; + deviceConfig.sampleRate = 0; + deviceConfig.dataCallback = data_callback; + deviceConfig.pUserData = &decoder; + deviceConfig.periodSizeInFrames = 1024; + + result = ma_device_init(NULL, &deviceConfig, &device); + if (result != MA_SUCCESS) { + debugPrint("Failed to initialize device.\n"); + return 1; + } + + /* Initialize the waveform before starting the device. */ + { + ma_waveform_config waveformConfig = ma_waveform_config_init(device.playback.format, device.playback.channels, device.sampleRate, ma_waveform_type_sine, 0.1f, 400); + ma_waveform_init(&waveformConfig, &waveform); + } + + /* Decoder. */ + { + ma_decoder_config decoderConfig = ma_decoder_config_init(device.playback.format, device.playback.channels, device.sampleRate); + ma_decoder_init_file("D:\\test.mp3", &decoderConfig, &decoder); + } + + result = ma_device_start(&device); + if (result != MA_SUCCESS) { + debugPrint("Failed to start device.\n"); + return 1; + } + + for (;;) { + /*debugPrint("Looping...\n");*/ + if (ma_device_get_threading_mode(&device) == MA_THREADING_MODE_SINGLE_THREADED) { + ma_device_step(&device, MA_BLOCKING_MODE_NON_BLOCKING); + } else { + ma_sleep(500); + } + } + + ma_waveform_uninit(&waveform); + ma_device_uninit(&device); + return 0; +}