Add PS Vita backend.

This commit is contained in:
David Reid
2026-03-01 07:53:00 +10:00
parent d7ce0506f6
commit bc52a82903
5 changed files with 569 additions and 0 deletions
+408
View File
@@ -3740,6 +3740,7 @@ example, ALSA, which is specific to Linux, will not be included in the Windows b
| Web Audio | ma_device_backend_webaudio | Web (via Emscripten) |
| Dreamcast | ma_device_backend_dreamcast | KallistiOS |
| XAudio | ma_device_backend_xaudio | NXDK |
| PS Vita | ma_device_backend_vita | Vita SDK |
| Null | ma_device_backend_null | Cross Platform (not used on Web) |
+-------------+------------------------------+--------------------------------------------------------+
@@ -3883,6 +3884,29 @@ use a period size smaller than 1024, but in my testing I found that it can resul
are not forced to use this configuration, but if you deviate from it miniaudio will need to do data
conversion.
15.8. PlayStation Vita
----------------------
The Vita backend uses Vita SDK.
The following device configuration is optimal:
Format: ma_foramt_s16
Channels: 2
Sample Rate: 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100 or 48000
Period Size: Multiple of 64, between 64 and 65472
noFixedSizedCallback: True
If you follow this configuration you can be guaranteed an optimized passthrough pipeline, except
when using a sample rate other than 48000 on the Main port (see below).
The BGM port (SCE_AUDIO_OUT_PORT_TYPE_BGM) is used by default. You can use the Main port by
configuring it in the device config:
deviceConfig.vita.portType = MA_VITA_PORT_TYPE_MAIN; // Or MA_VITA_PORT_TYPE_BGM
When using MA_VITA_PORT_TYPE_MAIN, the native sample rate will always be 48000 and using anything
else will invoke miniaudio's resampler which will have overhead.
16. Optimization Tips
=====================
@@ -7482,6 +7506,34 @@ MA_API ma_device_config_xaudio ma_device_config_xaudio_init(void);
/* END miniaudio_xaudio.h */
/* BEG miniaudio_vita.h */
extern ma_device_backend_vtable* ma_device_backend_vita;
MA_API ma_device_backend_vtable* ma_vita_get_vtable(void);
typedef struct
{
int _unused;
} ma_context_config_vita;
MA_API ma_context_config_vita ma_context_config_vita_init(void);
typedef enum
{
MA_VITA_PORT_TYPE_BGM = 0,
MA_VITA_PORT_TYPE_MAIN = 1
} ma_vita_port_type;
typedef struct
{
ma_vita_port_type portType;
} ma_device_config_vita;
MA_API ma_device_config_vita ma_device_config_vita_init(void);
/* END miniaudio_vita.h */
/* BEG miniaudio_null.h */
typedef struct ma_context_config_null
{
@@ -7914,6 +7966,7 @@ struct ma_device_config
ma_device_config_webaudio webaudio;
ma_device_config_dreamcast dreamcast;
ma_device_config_xaudio xaudio;
ma_device_config_vita vita;
ma_device_config_null null_backend;
};
@@ -8055,6 +8108,7 @@ struct ma_context_config
ma_context_config_webaudio webaudio;
ma_context_config_dreamcast dreamcast;
ma_context_config_xaudio xaudio;
ma_context_config_vita vita;
ma_context_config_null null_backend;
};
@@ -20338,6 +20392,9 @@ BACKENDS
#if defined(MA_XBOX)
#define MA_SUPPORT_XAUDIO
#endif
#if defined(MA_VITA)
#define MA_SUPPORT_VITA
#endif
/* All platforms should support custom backends. */
#define MA_SUPPORT_CUSTOM
@@ -20396,6 +20453,9 @@ BACKENDS
#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_VITA) && !defined(MA_NO_VITA) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_VITA))
#define MA_HAS_VITA
#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
@@ -48766,6 +48826,347 @@ MA_API ma_device_config_xaudio ma_device_config_xaudio_init(void)
/* END miniaudio_xaudio.c */
/* BEG miniaudio_vita.c */
#if defined(MA_HAS_VITA)
#include <psp2/audioout.h>
typedef struct ma_context_state_vita
{
int _unused;
} ma_context_state_vita;
typedef struct ma_device_state_vita
{
int port;
ma_bool32 isRunning; /* Used for tracking whether or not the background thread should be terminated. */
ma_thread thread; /* Need to call sceAudioOutOutput() from a separate thread because it is always blocking. */
ma_uint32 subBufferIndex;
ma_uint32 validSubBufferCount;
void* pSubBuffers;
} ma_device_state_vita;
static ma_context_state_vita* ma_context_get_backend_state__vita(ma_context* pContext)
{
return (ma_context_state_vita*)ma_context_get_backend_state(pContext);
}
static ma_device_state_vita* ma_device_get_backend_state__vita(ma_device* pDevice)
{
return (ma_device_state_vita*)ma_device_get_backend_state(pDevice);
}
static void ma_backend_info__vita(ma_device_backend_info* pBackendInfo)
{
pBackendInfo->pName = "PlayStation Vita";
}
static ma_result ma_context_init__vita(ma_context* pContext, const void* pContextBackendConfig, void** ppContextState)
{
ma_context_state_vita* pContextStateVita;
const ma_context_config_vita* pContextConfigVita = (ma_context_config_vita*)pContextBackendConfig;
pContextStateVita = (ma_context_state_vita*)ma_calloc(sizeof(*pContextStateVita), ma_context_get_allocation_callbacks(pContext));
if (pContextStateVita == NULL) {
return MA_OUT_OF_MEMORY;
}
(void)pContextConfigVita;
(void)pContext;
*ppContextState = pContextStateVita;
return MA_SUCCESS;
}
static void ma_context_uninit__vita(ma_context* pContext)
{
ma_context_state_vita* pContextStateVita = ma_context_get_backend_state__vita(pContext);
ma_free(pContextStateVita, ma_context_get_allocation_callbacks(pContext));
}
static ma_result ma_context_enumerate_devices__vita(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pCallbackUserData)
{
ma_context_state_vita* pContextStateVita = ma_context_get_backend_state__vita(pContext);
ma_device_info deviceInfo;
ma_device_enumeration_result enumerationResult;
(void)pContextStateVita;
/* Playback. */
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);
/* Only s16 and mono/stereo is natively supported. */
ma_device_info_add_native_data_format(&deviceInfo, ma_format_s16, 1, 2, 8000, 48000);
enumerationResult = callback(ma_device_type_playback, &deviceInfo, pCallbackUserData);
if (enumerationResult == MA_DEVICE_ENUMERATION_ABORT) {
return MA_SUCCESS;
}
return MA_SUCCESS;
}
static void* ma_device_get_sub_buffer__vita(ma_device* pDevice, ma_uint32 subBufferIndex)
{
ma_device_state_vita* pDeviceStateVita = ma_device_get_backend_state__vita(pDevice);
return ma_offset_ptr(pDeviceStateVita->pSubBuffers, pDevice->playback.internalPeriodSizeInFrames * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels) * subBufferIndex);
}
static ma_thread_result MA_THREADCALL ma_device_audio_thread__vita(void* pUserData)
{
ma_device* pDevice = (ma_device*)pUserData;
ma_device_state_vita* pDeviceStateVita = ma_device_get_backend_state__vita(pDevice);
while (ma_atomic_load_explicit_32(&pDeviceStateVita->isRunning, ma_atomic_memory_order_relaxed)) {
if (ma_atomic_load_explicit_32(&pDeviceStateVita->validSubBufferCount, ma_atomic_memory_order_acquire) > 0) {
ma_uint32 subBufferIndex = ma_atomic_load_explicit_32(&pDeviceStateVita->subBufferIndex, ma_atomic_memory_order_relaxed);
sceAudioOutOutput(pDeviceStateVita->port, ma_device_get_sub_buffer__vita(pDevice, subBufferIndex));
ma_atomic_store_explicit_32(&pDeviceStateVita->subBufferIndex, (subBufferIndex + 1) & 1, ma_atomic_memory_order_relaxed);
ma_atomic_fetch_sub_explicit_32(&pDeviceStateVita->validSubBufferCount, 1, ma_atomic_memory_order_release);
} else {
/* Just a dumb sleep so we don't peg the CPU while the device is not stopped. */
ma_sleep(10);
}
}
return (ma_thread_result)0;
}
static ma_result ma_device_init__vita(ma_device* pDevice, const void* pDeviceBackendConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture, void** ppDeviceState)
{
ma_result result;
ma_device_state_vita* pDeviceStateVita;
ma_device_config_vita* pDeviceConfigVita = (ma_device_config_vita*)pDeviceBackendConfig;
ma_context_state_vita* pContextStateVita = ma_context_get_backend_state__vita(ma_device_get_context(pDevice));
ma_device_config_vita defaultConfig;
ma_device_type deviceType = ma_device_get_type(pDevice);
ma_log* pLog = ma_device_get_log(pDevice);
ma_format format;
ma_uint32 channels;
ma_uint32 sampleRate;
ma_uint32 periodSizeInFrames;
SceAudioOutPortType portType;
(void)pContextStateVita;
(void)pDescriptorCapture;
/* Use a default config if one was not provided. This is not mandated by miniaudio, but it's good practice. */
if (pDeviceConfigVita == NULL) {
defaultConfig = ma_device_config_vita_init();
pDeviceConfigVita = &defaultConfig;
}
/* Return an error for any unsupported device types. */
if (deviceType != ma_device_type_playback) {
return MA_DEVICE_TYPE_NOT_SUPPORTED;
}
pDeviceStateVita = (ma_device_state_vita*)ma_calloc(sizeof(*pDeviceStateVita), ma_device_get_allocation_callbacks(pDevice));
if (pDeviceStateVita == NULL) {
return MA_OUT_OF_MEMORY;
}
/* Port type. */
portType = SCE_AUDIO_OUT_PORT_TYPE_BGM;
if (pDeviceConfigVita->portType == MA_VITA_PORT_TYPE_MAIN) {
portType = SCE_AUDIO_OUT_PORT_TYPE_MAIN;
}
/* Format is always s16. */
format = ma_format_s16;
/* Channels is always mono or stereo. Default to stereo. */
channels = pDescriptorPlayback->channels;
if (channels != 1 && channels != 2) {
channels = 1;
}
/* Sample rate. */
sampleRate = 0;
if (pDescriptorPlayback->sampleRate != 0) {
if (portType == SCE_AUDIO_OUT_PORT_TYPE_BGM) {
ma_uint32 bgmSampleRates[] = {8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000};
ma_uint32 iSampleRate;
for (iSampleRate = 0; iSampleRate < ma_countof(bgmSampleRates); iSampleRate += 1) {
if (pDescriptorPlayback->sampleRate == bgmSampleRates[iSampleRate]) {
sampleRate = bgmSampleRates[iSampleRate];
}
}
}
}
if (sampleRate == 0) {
sampleRate = 48000;
}
/* The period size must be a multiple of 64. */
periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptorPlayback, sampleRate);
periodSizeInFrames = ma_clamp((periodSizeInFrames + 63) & ~63, SCE_AUDIO_MIN_LEN, SCE_AUDIO_MAX_LEN);
pDeviceStateVita->pSubBuffers = ma_malloc(periodSizeInFrames * ma_get_bytes_per_frame(format, channels) * 2, ma_device_get_allocation_callbacks(pDevice));
if (pDeviceStateVita->pSubBuffers == NULL) {
return MA_OUT_OF_MEMORY;
}
pDeviceStateVita->port = sceAudioOutOpenPort(portType, (int)periodSizeInFrames, (int)sampleRate, (channels == 1) ? SCE_AUDIO_OUT_MODE_MONO : SCE_AUDIO_OUT_MODE_STEREO);
if (pDeviceStateVita->port < 0) {
ma_free(pDeviceStateVita->pSubBuffers, ma_device_get_allocation_callbacks(pDevice));
ma_log_postf(pLog, MA_LOG_LEVEL_ERROR, "[Vita] Failed to open port.");
return MA_ERROR;
}
/* Make sure the running status is set appropriately so the audio thread doesn't immediately terminate itself. */
ma_atomic_store_explicit_32(&pDeviceStateVita->isRunning, 1, ma_atomic_memory_order_relaxed);
/*
Because sceAudioOutOutput() is always blocking, in order to do non-blocking processing, we'll need to call
it on a separate thread. Make sure the thread is created last.
*/
result = ma_thread_create(&pDeviceStateVita->thread, ma_thread_priority_default, 0, ma_device_audio_thread__vita, pDevice, ma_device_get_allocation_callbacks(pDevice));
if (result != MA_SUCCESS) {
sceAudioOutReleasePort(pDeviceStateVita->port);
ma_free(pDeviceStateVita->pSubBuffers, ma_device_get_allocation_callbacks(pDevice));
ma_log_postf(pLog, MA_LOG_LEVEL_ERROR, "[Vita] Failed to create audio thread.");
return MA_ERROR;
}
/* Update the descriptor with the actual internal settings. */
pDescriptorPlayback->format = format;
pDescriptorPlayback->channels = channels;
pDescriptorPlayback->sampleRate = sampleRate;
pDescriptorPlayback->periodSizeInFrames = periodSizeInFrames;
pDescriptorPlayback->periodCount = 2;
*ppDeviceState = pDeviceStateVita;
return MA_SUCCESS;
}
static void ma_device_uninit__vita(ma_device* pDevice)
{
ma_device_state_vita* pDeviceStateVita = ma_device_get_backend_state__vita(pDevice);
/* Kill the thread first. */
ma_atomic_store_explicit_32(&pDeviceStateVita->isRunning, 0, ma_atomic_memory_order_relaxed);
ma_thread_wait(&pDeviceStateVita->thread);
sceAudioOutReleasePort(pDeviceStateVita->port);
ma_free(pDeviceStateVita->pSubBuffers, ma_device_get_allocation_callbacks(pDevice));
ma_free(pDeviceStateVita, ma_device_get_allocation_callbacks(pDevice));
}
static ma_result ma_device_start__vita(ma_device* pDevice)
{
ma_device_state_vita* pDeviceStateVita = ma_device_get_backend_state__vita(pDevice);
ma_atomic_store_explicit_32(&pDeviceStateVita->subBufferIndex, 0, ma_atomic_memory_order_relaxed);
ma_atomic_store_explicit_32(&pDeviceStateVita->validSubBufferCount, 0, ma_atomic_memory_order_relaxed);
/* Don't actually do anything here. We start by simply outputting data. Stopping is just not outputting data. */
return MA_SUCCESS;
}
static ma_result ma_device_stop__vita(ma_device* pDevice)
{
ma_device_state_vita* pDeviceStateVita = ma_device_get_backend_state__vita(pDevice);
/* Wait for the buffers to be drained. */
while (ma_atomic_load_explicit_32(&pDeviceStateVita->validSubBufferCount, ma_atomic_memory_order_relaxed) > 0) {
ma_sleep(1);
}
return MA_SUCCESS;
}
static ma_result ma_device_step__vita(ma_device* pDevice, ma_blocking_mode blockingMode)
{
ma_device_state_vita* pDeviceStateVita = ma_device_get_backend_state__vita(pDevice);
for (;;) {
if (!ma_device_is_started(pDevice)) {
return MA_DEVICE_NOT_STARTED;
}
if (ma_atomic_load_explicit_32(&pDeviceStateVita->validSubBufferCount, ma_atomic_memory_order_acquire) < 2) {
ma_uint32 subBufferIndex = ma_atomic_load_explicit_32(&pDeviceStateVita->subBufferIndex, ma_atomic_memory_order_relaxed);
ma_device_handle_backend_data_callback(pDevice, ma_device_get_sub_buffer__vita(pDevice, subBufferIndex), NULL, pDevice->playback.internalPeriodSizeInFrames);
ma_atomic_fetch_add_explicit_32(&pDeviceStateVita->validSubBufferCount, 1, ma_atomic_memory_order_release);
return MA_SUCCESS;
}
/* Getting here means there was no data to process. */
if (blockingMode == MA_BLOCKING_MODE_NON_BLOCKING) {
return MA_SUCCESS;
}
/* Getting here means there was no data to process and we're running in blocking mode. Sleep for a bit and keep trying. */
ma_sleep(1);
}
}
static void ma_device_wakeup__vita(ma_device* pDevice)
{
/* Nothing to do here. */
(void)pDevice;
}
static ma_device_backend_vtable ma_gDeviceBackendVTable_Vita =
{
ma_backend_info__vita,
ma_context_init__vita,
ma_context_uninit__vita,
ma_context_enumerate_devices__vita,
ma_device_init__vita,
ma_device_uninit__vita,
ma_device_start__vita,
ma_device_stop__vita,
ma_device_step__vita,
ma_device_wakeup__vita
};
ma_device_backend_vtable* ma_device_backend_vita = &ma_gDeviceBackendVTable_Vita;
#else
ma_device_backend_vtable* ma_device_backend_vita = NULL;
#endif /* MA_HAS_VITA */
MA_API ma_device_backend_vtable* ma_vita_get_vtable(void)
{
return ma_device_backend_vita;
}
MA_API ma_context_config_vita ma_context_config_vita_init(void)
{
ma_context_config_vita config;
MA_ZERO_OBJECT(&config);
return config;
}
MA_API ma_device_config_vita ma_device_config_vita_init(void)
{
ma_device_config_vita config;
MA_ZERO_OBJECT(&config);
config.portType = MA_VITA_PORT_TYPE_BGM;
return config;
}
/* END miniaudio_vita.c */
MA_API void ma_get_device_backend_info(ma_device_backend_vtable* pBackendVTable, ma_device_backend_info* pBackendInfo)
{
@@ -49800,6 +50201,9 @@ static const void* ma_context_config_find_backend_config(const ma_context_config
if (pVTable == ma_device_backend_xaudio) {
return &pConfig->xaudio;
}
if (pVTable == ma_device_backend_vita) {
return &pConfig->vita;
}
if (pVTable == ma_device_backend_null) {
return &pConfig->null_backend;
}
@@ -49831,6 +50235,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_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_vita, NULL); }
if (backendsCap > count) { pBackends[count++] = ma_device_backend_config_init(ma_device_backend_null, NULL); }
return count;
@@ -50302,6 +50707,9 @@ static const void* ma_device_config_find_backend_config(const ma_device_config*
if (pVTable == ma_device_backend_xaudio) {
return &pConfig->xaudio;
}
if (pVTable == ma_device_backend_vita) {
return &pConfig->vita;
}
if (pVTable == ma_device_backend_null) {
return &pConfig->null_backend;
}