#define NX_SERVICE_ASSUME_NON_DOMAIN #include "service_guard.h" #include "kernel/tmem.h" #include "kernel/event.h" #include "runtime/hosversion.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* srv, Service* srv_out, const AudioRendererParameter* param); static Result _audrenGetWorkBufferSize(Service* srv, const AudioRendererParameter* param, u64* out_size); static Result _audrenQuerySystemEvent(Event* out_event); NX_GENERATE_SERVICE_GUARD_PARAMS(audren, (const AudioRendererConfig* config), (config)); Result _audrenInitialize(const AudioRendererConfig* config) { Result rc=0; // 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) u32 hosver = hosversionGet(); /*if (hosver >= MAKEHOSVERSION(6,1,0)) g_audrenRevision = AUDREN_REVISION_6; else if (hosver >= MAKEHOSVERSION(6,0,0)) g_audrenRevision = AUDREN_REVISION_5; else*/ if (hosver >= MAKEHOSVERSION(4,0,0)) g_audrenRevision = AUDREN_REVISION_4; else if (hosver >= MAKEHOSVERSION(3,0,0)) g_audrenRevision = AUDREN_REVISION_3; else if (hosver >= MAKEHOSVERSION(2,0,0)) g_audrenRevision = AUDREN_REVISION_2; else g_audrenRevision = AUDREN_REVISION_1; // 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; // Open IAudioRendererManager Service audrenMgrSrv; rc = smGetService(&audrenMgrSrv, "audren:u"); if (R_SUCCEEDED(rc)) { // Get required work buffer size u64 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, &g_audrenIAudioRenderer, ¶m); if (R_SUCCEEDED(rc)) { // Finally, get the handle to the system event rc = _audrenQuerySystemEvent(&g_audrenEvent); } } } serviceClose(&audrenMgrSrv); } return rc; } void _audrenCleanup(void) { eventClose(&g_audrenEvent); serviceClose(&g_audrenIAudioRenderer); tmemClose(&g_audrenWorkBuf); } Service* audrenGetServiceSession_AudioRenderer(void) { return &g_audrenIAudioRenderer; } static Result _audrenCmdGetHandle(Service* srv, Handle* handle_out, u32 cmd_id) { return serviceDispatch(srv, cmd_id, .out_handle_attrs = { SfOutHandleAttr_HipcCopy }, .out_handles = handle_out, ); } static Result _audrenCmdGetEvent(Service* srv, Event* out_event, bool autoclear, u32 cmd_id) { Handle tmp_handle = INVALID_HANDLE; Result rc = 0; rc = _audrenCmdGetHandle(srv, &tmp_handle, cmd_id); if (R_SUCCEEDED(rc)) eventLoadRemote(out_event, tmp_handle, autoclear); return rc; } static Result _audrenCmdNoIO(Service* srv, u32 cmd_id) { return serviceDispatch(srv, cmd_id); } static Result _audrenCmdNoInOutU32(Service* srv, u32 *out, u32 cmd_id) { return serviceDispatchOut(srv, cmd_id, *out); } void audrenWaitFrame(void) { eventWait(&g_audrenEvent, UINT64_MAX); } Result _audrenOpenAudioRenderer(Service* srv, Service* srv_out, const AudioRendererParameter* param) { const struct { AudioRendererParameter param; u32 pad; u64 work_buffer_size; u64 aruid; } in = { *param, 0, g_audrenWorkBuf.size, appletGetAppletResourceUserId() }; return serviceDispatchIn(srv, 0, in, .in_send_pid = true, .in_num_handles = 2, .in_handles = { g_audrenWorkBuf.handle, CUR_PROCESS_HANDLE }, .out_num_objects = 1, .out_objects = srv_out, ); } Result _audrenGetWorkBufferSize(Service* srv, const AudioRendererParameter* param, u64* out_size) { return serviceDispatchInOut(srv, 1, *param, *out_size); } Result audrenGetState(u32* out_state) { return _audrenCmdNoInOutU32(&g_audrenIAudioRenderer, out_state, 3); } 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) { bool new_cmd = hosversionAtLeast(3,0,0); u32 tmpattr = new_cmd==0 ? SfBufferAttr_HipcMapAlias : SfBufferAttr_HipcAutoSelect; return serviceDispatch(&g_audrenIAudioRenderer, new_cmd==0 ? 4 : 10, .buffer_attrs = { tmpattr | SfBufferAttr_Out, tmpattr | SfBufferAttr_Out, tmpattr | SfBufferAttr_In, }, .buffers = { { out_param_buf, out_param_buf_size }, { perf_buf, perf_buf_size }, { in_param_buf, in_param_buf_size }, }, ); } Result audrenStartAudioRenderer(void) { return _audrenCmdNoIO(&g_audrenIAudioRenderer, 5); } Result audrenStopAudioRenderer(void) { return _audrenCmdNoIO(&g_audrenIAudioRenderer, 6); } Result _audrenQuerySystemEvent(Event* out_event) { return _audrenCmdGetEvent(&g_audrenIAudioRenderer, out_event, true, 7); } Result audrenSetAudioRendererRenderingTimeLimit(int percent) { return serviceDispatchIn(&g_audrenIAudioRenderer, 8, percent); }