diff --git a/nx/include/switch.h b/nx/include/switch.h index b3ed59cc..266ffba8 100644 --- a/nx/include/switch.h +++ b/nx/include/switch.h @@ -44,6 +44,7 @@ extern "C" { #include "switch/services/applet.h" #include "switch/services/audin.h" #include "switch/services/audout.h" +#include "switch/services/audren.h" #include "switch/services/csrng.h" #include "switch/services/bpc.h" //#include "switch/services/bsd.h" Use switch/runtime/devices/socket.h instead diff --git a/nx/include/switch/services/audren.h b/nx/include/switch/services/audren.h new file mode 100644 index 00000000..d696dc97 --- /dev/null +++ b/nx/include/switch/services/audren.h @@ -0,0 +1,327 @@ +/** + * @file audren.h + * @brief Audio renderer service. + * @author fincs + * @copyright libnx Authors + */ +#pragma once + +#include "../audio/audio.h" + +#ifdef __cplusplus +#define AUDREN_CONSTEXPR constexpr +#else +#define AUDREN_CONSTEXPR static inline +#endif + +#define AUDREN_TIMER_FREQ_HZ 200.0f +#define AUDREN_TIMER_PERIOD_MS 5.0f +#define AUDREN_SAMPLES_PER_FRAME_32KHZ 160 +#define AUDREN_SAMPLES_PER_FRAME_48KHZ 240 + +#define AUDREN_INPUT_PARAM_ALIGNMENT 0x1000 +#define AUDREN_OUTPUT_PARAM_ALIGNMENT 0x10 +#define AUDREN_MEMPOOL_ALIGNMENT 0x1000 +#define AUDREN_BUFFER_ALIGNMENT 0x40 + +#define AUDREN_REVISION_1 0x31564552 // REV1 [1.0.0+?] +#define AUDREN_REVISION_2 0x32564552 // REV2 [2.0.0+?] +#define AUDREN_REVISION_3 0x33564552 // REV3 [3.0.0+?] +#define AUDREN_REVISION_4 0x34564552 // REV4 [somewhere after 3.0.0, possibly 4.0.0+?] + +#define AUDREN_NODEID(_a,_b,_c) ((((u32)(_a) & 0xF) << 28) | (((u32)(_b) & 0xFFF) << 16) | ((u32)(_c) & 0xFFFF)) +#define AUDREN_FINAL_MIX_ID 0 +#define AUDREN_UNUSED_MIX_ID 0x7FFFFFFF +#define AUDREN_UNUSED_SPLITTER_ID 0xFFFFFFFF + +#define AUDREN_DEFAULT_DEVICE_NAME "MainAudioOut" + +typedef enum { + AudioRendererOutputRate_32kHz, + AudioRendererOutputRate_48kHz, +} AudioRendererOutputRate; + +typedef struct { + AudioRendererOutputRate output_rate; + int num_voices; + int num_effects; + int num_sinks; + int num_mix_objs; + int num_mix_buffers; +} AudioRendererConfig; + +/* +Input buffer layout: + +AudioRendererUpdateDataHeader +AudioRendererBehaviorInfoIn +AudioRendererMemPoolInfoIn * mempool_count +AudioRendererChannelInfoIn * channel_count +AudioRendererVoiceInfoIn * voice_count +(effects would go here) +(splitters would go here) +AudioRendererMixInfoIn * mix_count (i.e. submix_count+1) +AudioRendererSinkInfoIn * sink_count +AudioRendererPerformanceBufferInfoIn +*/ + +/* +Output buffer layout: + +AudioRendererUpdateDataHeader +AudioRendererMemPoolInfoOut * mempool_count +AudioRendererVoiceInfoOut * voice_count +(effects would go here) +AudioRendererSinkInfoOut * sink_count +AudioRendererPerformanceBufferInfoOut +AudioRendererBehaviorInfoOut +*/ + +typedef struct { + u32 revision; + u32 behavior_sz; + u32 mempools_sz; + u32 voices_sz; + u32 channels_sz; + u32 effects_sz; + u32 mixes_sz; + u32 sinks_sz; + u32 perfmgr_sz; + u32 _padding[6]; + u32 total_sz; +} AudioRendererUpdateDataHeader; + +typedef struct { + u32 revision; + u32 _padding1; + u64 flags; +} AudioRendererBehaviorInfoIn; + +typedef struct { + u64 unknown[20]; + u64 _padding1[2]; +} AudioRendererBehaviorInfoOut; + +typedef enum { + AudioRendererMemPoolState_Invalid, + AudioRendererMemPoolState_New, + AudioRendererMemPoolState_RequestDetach, + AudioRendererMemPoolState_Detached, + AudioRendererMemPoolState_RequestAttach, + AudioRendererMemPoolState_Attached, + AudioRendererMemPoolState_Released, +} AudioRendererMemPoolState; + +typedef struct { + const void* address; + u64 size; + AudioRendererMemPoolState state; + u32 _padding2[3]; +} AudioRendererMemPoolInfoIn; + +typedef struct +{ + AudioRendererMemPoolState new_state; + u32 _padding2[3]; +} AudioRendererMemPoolInfoOut; + +typedef struct { + u32 id; + float mix[24]; + bool is_used; + u8 _padding1[11]; +} AudioRendererChannelInfoIn; + +typedef struct { + bool enable; + u8 _padding; + s16 numerator[3]; + s16 denominator[2]; +} AudioRendererBiquadFilter; + +typedef struct { + u16 coefficients[16]; +} AudioRendererAdpcmParameters; + +typedef struct { + u16 index; + s16 history0; + s16 history1; +} AudioRendererAdpcmContext; + +typedef struct { + const void* address; + u64 size; + s32 start_sample_offset; + s32 end_sample_offset; + bool is_looping; + bool end_of_stream; + bool sent_to_server; + u8 _padding1[5]; + const void* context_addr; + u64 context_sz; + u64 _padding2; +} AudioRendererWaveBuf; + +typedef enum { + AudioRendererVoicePlayState_Started, + AudioRendererVoicePlayState_Stopped, + AudioRendererVoicePlayState_Paused, +} AudioRendererVoicePlayState; + +typedef struct { + u32 id; + u32 node_id; + bool is_new; + bool is_used; + AudioRendererVoicePlayState state : 8; + PcmFormat sample_format : 8; + u32 sample_rate; + u32 priority; + u32 sorting_order; + u32 channel_count; + float pitch; + float volume; + AudioRendererBiquadFilter biquads[2]; + u32 wavebuf_count; + s16 wavebuf_head; + u16 _padding1; + u32 _padding2; + const void* extra_params_ptr; + u64 extra_params_sz; + u32 dest_mix_id; + u32 dest_splitter_id; + AudioRendererWaveBuf wavebufs[4]; + u32 channel_ids[6]; + u8 _padding3[24]; +} AudioRendererVoiceInfoIn; + +typedef struct { + u64 played_sample_count; + u32 num_wavebufs_consumed; + u32 voice_drops_count; +} AudioRendererVoiceInfoOut; + +typedef struct { + float volume; + u32 sample_rate; + u32 buffer_count; + bool is_used; + u8 _padding1[3]; + u32 mix_id; + u32 _padding2; + u32 node_id; + u32 _padding3[2]; + float mix[24][24]; // [src_index][dest_index] + u32 dest_mix_id; + u32 dest_splitter_id; + u32 _padding4; +} AudioRendererMixInfoIn; + +typedef struct { + u8 coefficients[16]; +} AudioRendererDownMixParameters; + +typedef enum { + AudioRendererSinkType_Invalid, + AudioRendererSinkType_Device, + AudioRendererSinkType_CircularBuffer, +} AudioRendererSinkType; + +typedef struct { + char name[255]; + u8 _padding1; + u32 input_count; + u8 inputs[6]; + u8 _padding2; + bool downmix_params_enabled; + AudioRendererDownMixParameters downmix_params; +} AudioRendererDeviceSinkInfoIn; + +typedef struct { + void* buffer_ptr; + u32 buffer_sz; + u32 input_count; + u32 sample_count; + u32 last_read_offset; + PcmFormat sample_format; + u8 inputs[6]; + u8 _padding2[6]; +} AudioRendererCircularBufferSinkInfoIn; + +typedef struct { + AudioRendererSinkType type : 8; + bool is_used; + u8 _padding1[2]; + u32 node_id; + u64 _padding2[3]; + union { + AudioRendererDeviceSinkInfoIn device_sink; + AudioRendererCircularBufferSinkInfoIn circular_buffer_sink; + }; +} AudioRendererSinkInfoIn; + +typedef struct { + u32 last_written_offset; + u32 unk1; + u64 unk2; + u64 _padding1[2]; +} AudioRendererSinkInfoOut; + +typedef struct { + u32 detail_target; + u32 _padding1[3]; +} AudioRendererPerformanceBufferInfoIn; + +typedef struct { + u32 written_sz; + u32 _padding1[3]; +} AudioRendererPerformanceBufferInfoOut; + +static inline u32 audrenGetRevision(void) +{ + extern u32 g_audrenRevision; + return g_audrenRevision; +} + +AUDREN_CONSTEXPR int audrenGetMemPoolCount(const AudioRendererConfig* config) +{ + return config->num_effects + 4 * config->num_voices; +} + +AUDREN_CONSTEXPR size_t audrenGetInputParamSize(const AudioRendererConfig* config) +{ + size_t size = 0; + size += sizeof(AudioRendererUpdateDataHeader); + size += sizeof(AudioRendererBehaviorInfoIn); + size += sizeof(AudioRendererMemPoolInfoIn) * audrenGetMemPoolCount(config); + size += sizeof(AudioRendererChannelInfoIn) * config->num_voices; + size += sizeof(AudioRendererVoiceInfoIn) * config->num_voices; + // todo: effects, splitters + size += sizeof(AudioRendererMixInfoIn) * config->num_mix_objs; + size += sizeof(AudioRendererSinkInfoIn) * config->num_sinks; + size += sizeof(AudioRendererPerformanceBufferInfoIn); + return size; +} + +AUDREN_CONSTEXPR size_t audrenGetOutputParamSize(const AudioRendererConfig* config) +{ + size_t size = 0; + size += sizeof(AudioRendererUpdateDataHeader); + size += sizeof(AudioRendererMemPoolInfoOut) * audrenGetMemPoolCount(config); + size += sizeof(AudioRendererVoiceInfoOut) * config->num_voices; + // todo: effects + size += sizeof(AudioRendererSinkInfoOut) * config->num_sinks; + size += sizeof(AudioRendererPerformanceBufferInfoOut); + size += sizeof(AudioRendererBehaviorInfoOut); + return size; +} + +Result audrenInitialize(const AudioRendererConfig* config); +void audrenExit(void); +void audrenWaitFrame(void); +Result audrenGetState(u32* out_state); +Result audrenRequestUpdateAudioRenderer(const void* in_param_buf, size_t in_param_buf_size, void* out_param_buf, size_t out_param_buf_size, void* perf_buf, size_t perf_buf_size); +Result audrenStartAudioRenderer(void); +Result audrenStopAudioRenderer(void); +Result audrenSetAudioRendererRenderingTimeLimit(int percent); diff --git a/nx/source/services/audren.c b/nx/source/services/audren.c new file mode 100644 index 00000000..2597e9e6 --- /dev/null +++ b/nx/source/services/audren.c @@ -0,0 +1,414 @@ +#include +#include "types.h" +#include "result.h" +#include "arm/atomics.h" +#include "kernel/ipc.h" +#include "kernel/tmem.h" +#include "kernel/event.h" +#include "services/sm.h" +#include "services/applet.h" +#include "services/audren.h" + +u32 g_audrenRevision; + +static Service g_audrenIAudioRenderer; +static TransferMemory g_audrenWorkBuf; +static Event g_audrenEvent; + +typedef struct { + s32 sample_rate; + s32 sample_count; + s32 mix_buffer_count; + s32 submix_count; + s32 voice_count; + s32 sink_count; + s32 effect_count; + s32 unk1; + u8 unk2; + u8 _padding1[3]; + s32 splitter_count; + s32 unk3; + s32 unk4; + u32 revision; +} AudioRendererParameter; + +static Result _audrenOpenAudioRenderer(Service* audren_mgr, const AudioRendererParameter* param, u64 aruid); +static Result _audrenGetWorkBufferSize(Service* audren_mgr, const AudioRendererParameter* param, u64* out_size); +static Result _audrenQuerySystemEvent(void); + +Result audrenInitialize(const AudioRendererConfig* config) +{ + Result rc; + if (serviceIsActive(&g_audrenIAudioRenderer)) + return MAKERESULT(Module_Libnx, LibnxError_AlreadyInitialized); + + // Validate configuration + if (config->num_voices < 1 || config->num_voices > 1024) + return MAKERESULT(Module_Libnx, LibnxError_BadInput); + if (config->num_effects < 0 || config->num_effects > 256) + return MAKERESULT(Module_Libnx, LibnxError_BadInput); + if (config->num_sinks < 1 || config->num_sinks > 256) + return MAKERESULT(Module_Libnx, LibnxError_BadInput); + if (config->num_mix_objs < 1 || config->num_mix_objs > 256) + return MAKERESULT(Module_Libnx, LibnxError_BadInput); + if (config->num_mix_buffers < 1 || config->num_mix_buffers > 256) + return MAKERESULT(Module_Libnx, LibnxError_BadInput); + + // Choose revision (i.e. if splitters are used then at least revision 2 must be used) + // (TODO: abstract away differences between revisions according to provided parameters) + g_audrenRevision = AUDREN_REVISION_3; // todo: ?!? + + // Prepare parameter structure + AudioRendererParameter param = {0}; + param.sample_rate = config->output_rate == AudioRendererOutputRate_32kHz ? 32000 : 48000; + param.sample_count = config->output_rate == AudioRendererOutputRate_32kHz ? AUDREN_SAMPLES_PER_FRAME_32KHZ : AUDREN_SAMPLES_PER_FRAME_48KHZ; + param.mix_buffer_count = config->num_mix_buffers; + param.submix_count = config->num_mix_objs - 1; + param.voice_count = config->num_voices; + param.sink_count = config->num_sinks; + param.effect_count = config->num_effects; + param.revision = g_audrenRevision; + + // Get aruid + u64 aruid = 0; + rc = appletGetAppletResourceUserId(&aruid); + //if (R_FAILED(rc)) return rc; // apparently audren still inits fine with aruid = 0 so this isn't a fatal error condition + + // Open IAudioRendererManager + Service audrenMgrSrv; + rc = smGetService(&audrenMgrSrv, "audren:u"); + if (R_SUCCEEDED(rc)) + { + // Get required work buffer size + size_t workBufSize = 0; + rc = _audrenGetWorkBufferSize(&audrenMgrSrv, ¶m, &workBufSize); + if (R_SUCCEEDED(rc)) + { + // Create transfermem work buffer object + workBufSize = (workBufSize + 0xFFF) &~ 0xFFF; // 1.x fails hard and returns a non-page-aligned work buffer size + rc = tmemCreate(&g_audrenWorkBuf, workBufSize, Perm_None); + if (R_SUCCEEDED(rc)) + { + // Create the IAudioRenderer service + rc = _audrenOpenAudioRenderer(&audrenMgrSrv, ¶m, aruid); + if (R_SUCCEEDED(rc)) + { + // Finally, get the handle to the system event + rc = _audrenQuerySystemEvent(); + if (R_FAILED(rc)) + serviceClose(&g_audrenIAudioRenderer); + } + if (R_FAILED(rc)) + tmemClose(&g_audrenWorkBuf); + } + } + serviceClose(&audrenMgrSrv); + } + return rc; +} + +void audrenExit(void) +{ + if (!serviceIsActive(&g_audrenIAudioRenderer)) + return; + + eventClose(&g_audrenEvent); + serviceClose(&g_audrenIAudioRenderer); + tmemClose(&g_audrenWorkBuf); +} + +void audrenWaitFrame(void) +{ + eventWait(&g_audrenEvent, U64_MAX); + eventClear(&g_audrenEvent); +} + +Result _audrenOpenAudioRenderer(Service* audren_mgr, const AudioRendererParameter* param, u64 aruid) +{ + IpcCommand c; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + + AudioRendererParameter param; + u64 work_buffer_size; + u64 aruid; + } *raw; + + ipcSendPid(&c); + ipcSendHandleCopy(&c, g_audrenWorkBuf.handle); + ipcSendHandleCopy(&c, CUR_PROCESS_HANDLE); + + raw = ipcPrepareHeader(&c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 0; + raw->param = *param; + raw->work_buffer_size = g_audrenWorkBuf.size; + raw->aruid = aruid; + + Result rc = serviceIpcDispatch(audren_mgr); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + ipcParse(&r); + + struct { + u64 magic; + u64 result; + } *resp = r.Raw; + + rc = resp->result; + + if (R_SUCCEEDED(rc)) + serviceCreate(&g_audrenIAudioRenderer, r.Handles[0]); + } + + return rc; +} + +Result _audrenGetWorkBufferSize(Service* audren_mgr, const AudioRendererParameter* param, size_t* out_size) +{ + IpcCommand c; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + + AudioRendererParameter param; + } *raw; + + raw = ipcPrepareHeader(&c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 1; + raw->param = *param; + + Result rc = serviceIpcDispatch(audren_mgr); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + ipcParse(&r); + + struct { + u64 magic; + u64 result; + u64 work_buf_size; + } *resp = r.Raw; + + rc = resp->result; + + if (R_SUCCEEDED(rc) && out_size) + *out_size = resp->work_buf_size; + } + + return rc; +} + +Result audrenGetState(u32* out_state) +{ + IpcCommand c; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + } *raw; + + raw = ipcPrepareHeader(&c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 3; + + Result rc = serviceIpcDispatch(&g_audrenIAudioRenderer); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + ipcParse(&r); + + struct { + u64 magic; + u64 result; + u32 state; + } *resp = r.Raw; + + rc = resp->result; + + if (R_SUCCEEDED(rc) && out_state) + *out_state = resp->state; + } + + return rc; +} + +Result audrenRequestUpdateAudioRenderer(const void* in_param_buf, size_t in_param_buf_size, void* out_param_buf, size_t out_param_buf_size, void* perf_buf, size_t perf_buf_size) +{ + IpcCommand c; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + } *raw; + + ipcAddSendBuffer(&c, in_param_buf, in_param_buf_size, BufferType_Normal); + ipcAddRecvBuffer(&c, out_param_buf, out_param_buf_size, BufferType_Normal); + ipcAddRecvBuffer(&c, perf_buf, perf_buf_size, BufferType_Normal); + + raw = ipcPrepareHeader(&c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 4; + + Result rc = serviceIpcDispatch(&g_audrenIAudioRenderer); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + ipcParse(&r); + + struct { + u64 magic; + u64 result; + } *resp = r.Raw; + + rc = resp->result; + } + + return rc; +} + +Result audrenStartAudioRenderer(void) +{ + IpcCommand c; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + } *raw; + + raw = ipcPrepareHeader(&c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 5; + + Result rc = serviceIpcDispatch(&g_audrenIAudioRenderer); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + ipcParse(&r); + + struct { + u64 magic; + u64 result; + } *resp = r.Raw; + + rc = resp->result; + } + + return rc; +} + +Result audrenStopAudioRenderer(void) +{ + IpcCommand c; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + } *raw; + + raw = ipcPrepareHeader(&c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 6; + + Result rc = serviceIpcDispatch(&g_audrenIAudioRenderer); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + ipcParse(&r); + + struct { + u64 magic; + u64 result; + } *resp = r.Raw; + + rc = resp->result; + } + + return rc; +} + +Result _audrenQuerySystemEvent(void) +{ + IpcCommand c; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + } *raw; + + raw = ipcPrepareHeader(&c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 7; + + Result rc = serviceIpcDispatch(&g_audrenIAudioRenderer); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + ipcParse(&r); + + struct { + u64 magic; + u64 result; + } *resp = r.Raw; + + rc = resp->result; + + if (R_SUCCEEDED(rc)) + eventLoadRemote(&g_audrenEvent, r.Handles[0]); + } + + return rc; +} + +Result audrenSetAudioRendererRenderingTimeLimit(int percent) +{ + IpcCommand c; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + int percent; + } *raw; + + raw = ipcPrepareHeader(&c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 8; + raw->percent = percent; + + Result rc = serviceIpcDispatch(&g_audrenIAudioRenderer); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + ipcParse(&r); + + struct { + u64 magic; + u64 result; + } *resp = r.Raw; + + rc = resp->result; + } + + return rc; +}