// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include #include #include "audio_core/audio_core.h" #include "audio_core/common/audio_renderer_parameter.h" #include "audio_core/common/feature_support.h" #include "audio_core/renderer/audio_device.h" #include "audio_core/renderer/audio_renderer.h" #include "audio_core/renderer/voice/voice_info.h" #include "common/alignment.h" #include "common/bit_util.h" #include "common/common_funcs.h" #include "common/logging/log.h" #include "common/polyfill_ranges.h" #include "common/scratch_buffer.h" #include "common/string_util.h" #include "core/core.h" #include "core/hle/kernel/k_event.h" #include "core/hle/kernel/k_process.h" #include "core/hle/kernel/k_transfer_memory.h" #include "core/hle/service/audio/audren_u.h" #include "core/hle/service/audio/errors.h" #include "core/hle/service/ipc_helpers.h" #include "core/memory.h" using namespace AudioCore::Renderer; namespace Service::Audio { class IAudioRenderer final : public ServiceFramework { public: explicit IAudioRenderer(Core::System& system_, Manager& manager_, AudioCore::AudioRendererParameterInternal& params, Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size, u32 process_handle, Kernel::KProcess& process_, u64 applet_resource_user_id, s32 session_id) : ServiceFramework{system_, "IAudioRenderer"}, service_context{system_, "IAudioRenderer"}, rendered_event{service_context.CreateEvent("IAudioRendererEvent")}, manager{manager_}, impl{std::make_unique(system_, manager, rendered_event)}, process{process_} { // clang-format off static const FunctionInfo functions[] = { {0, &IAudioRenderer::GetSampleRate, "GetSampleRate"}, {1, &IAudioRenderer::GetSampleCount, "GetSampleCount"}, {2, &IAudioRenderer::GetMixBufferCount, "GetMixBufferCount"}, {3, &IAudioRenderer::GetState, "GetState"}, {4, &IAudioRenderer::RequestUpdate, "RequestUpdate"}, {5, &IAudioRenderer::Start, "Start"}, {6, &IAudioRenderer::Stop, "Stop"}, {7, &IAudioRenderer::QuerySystemEvent, "QuerySystemEvent"}, {8, &IAudioRenderer::SetRenderingTimeLimit, "SetRenderingTimeLimit"}, {9, &IAudioRenderer::GetRenderingTimeLimit, "GetRenderingTimeLimit"}, {10, &IAudioRenderer::RequestUpdate, "RequestUpdateAuto"}, {11, nullptr, "ExecuteAudioRendererRendering"}, {12, &IAudioRenderer::SetVoiceDropParameter, "SetVoiceDropParameter"}, {13, &IAudioRenderer::GetVoiceDropParameter, "GetVoiceDropParameter"}, }; // clang-format on RegisterHandlers(functions); process.Open(); impl->Initialize(params, transfer_memory, transfer_memory_size, process_handle, process, applet_resource_user_id, session_id); } ~IAudioRenderer() override { impl->Finalize(); service_context.CloseEvent(rendered_event); process.Close(); } private: void GetSampleRate(HLERequestContext& ctx) { const auto sample_rate{impl->GetSystem().GetSampleRate()}; LOG_DEBUG(Service_Audio, "called. Sample rate {}", sample_rate); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); rb.Push(sample_rate); } void GetSampleCount(HLERequestContext& ctx) { const auto sample_count{impl->GetSystem().GetSampleCount()}; LOG_DEBUG(Service_Audio, "called. Sample count {}", sample_count); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); rb.Push(sample_count); } void GetState(HLERequestContext& ctx) { const u32 state{!impl->GetSystem().IsActive()}; LOG_DEBUG(Service_Audio, "called, state {}", state); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); rb.Push(state); } void GetMixBufferCount(HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "called"); const auto buffer_count{impl->GetSystem().GetMixBufferCount()}; IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); rb.Push(buffer_count); } void RequestUpdate(HLERequestContext& ctx) { LOG_TRACE(Service_Audio, "called"); const auto input{ctx.ReadBuffer(0)}; // These buffers are written manually to avoid an issue with WriteBuffer throwing errors for // checking size 0. Performance size is 0 for most games. auto is_buffer_b{ctx.BufferDescriptorB()[0].Size() != 0}; if (is_buffer_b) { const auto buffersB{ctx.BufferDescriptorB()}; output_buffer.resize_destructive(buffersB[0].Size()); performance_buffer.resize_destructive(buffersB[1].Size()); } else { const auto buffersC{ctx.BufferDescriptorC()}; output_buffer.resize_destructive(buffersC[0].Size()); performance_buffer.resize_destructive(buffersC[1].Size()); } auto result = impl->RequestUpdate(input, performance_buffer, output_buffer); if (result.IsSuccess()) { if (is_buffer_b) { ctx.WriteBufferB(output_buffer.data(), output_buffer.size(), 0); ctx.WriteBufferB(performance_buffer.data(), performance_buffer.size(), 1); } else { ctx.WriteBufferC(output_buffer.data(), output_buffer.size(), 0); ctx.WriteBufferC(performance_buffer.data(), performance_buffer.size(), 1); } } else { LOG_ERROR(Service_Audio, "RequestUpdate failed error 0x{:02X}!", result.GetDescription()); } IPC::ResponseBuilder rb{ctx, 2}; rb.Push(result); } void Start(HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "called"); impl->Start(); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } void Stop(HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "called"); impl->Stop(); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } void QuerySystemEvent(HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "called"); if (impl->GetSystem().GetExecutionMode() == AudioCore::ExecutionMode::Manual) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(Audio::ResultNotSupported); return; } IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(ResultSuccess); rb.PushCopyObjects(rendered_event->GetReadableEvent()); } void SetRenderingTimeLimit(HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "called"); IPC::RequestParser rp{ctx}; auto limit = rp.PopRaw(); auto& system_ = impl->GetSystem(); system_.SetRenderingTimeLimit(limit); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } void GetRenderingTimeLimit(HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "called"); auto& system_ = impl->GetSystem(); auto time = system_.GetRenderingTimeLimit(); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); rb.Push(time); } void ExecuteAudioRendererRendering(HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "called"); } void SetVoiceDropParameter(HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "called"); IPC::RequestParser rp{ctx}; auto voice_drop_param{rp.Pop()}; auto& system_ = impl->GetSystem(); system_.SetVoiceDropParameter(voice_drop_param); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } void GetVoiceDropParameter(HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "called"); auto& system_ = impl->GetSystem(); auto voice_drop_param{system_.GetVoiceDropParameter()}; IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); rb.Push(voice_drop_param); } KernelHelpers::ServiceContext service_context; Kernel::KEvent* rendered_event; Manager& manager; std::unique_ptr impl; Kernel::KProcess& process; Common::ScratchBuffer output_buffer; Common::ScratchBuffer performance_buffer; }; class IAudioDevice final : public ServiceFramework { public: explicit IAudioDevice(Core::System& system_, u64 applet_resource_user_id, u32 revision, u32 device_num) : ServiceFramework{system_, "IAudioDevice"}, service_context{system_, "IAudioDevice"}, impl{std::make_unique(system_, applet_resource_user_id, revision)}, event{service_context.CreateEvent(fmt::format("IAudioDeviceEvent-{}", device_num))} { static const FunctionInfo functions[] = { {0, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceName"}, {1, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolume"}, {2, &IAudioDevice::GetAudioDeviceOutputVolume, "GetAudioDeviceOutputVolume"}, {3, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioDeviceName"}, {4, &IAudioDevice::QueryAudioDeviceSystemEvent, "QueryAudioDeviceSystemEvent"}, {5, &IAudioDevice::GetActiveChannelCount, "GetActiveChannelCount"}, {6, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceNameAuto"}, {7, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolumeAuto"}, {8, &IAudioDevice::GetAudioDeviceOutputVolume, "GetAudioDeviceOutputVolumeAuto"}, {10, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioDeviceNameAuto"}, {11, &IAudioDevice::QueryAudioDeviceInputEvent, "QueryAudioDeviceInputEvent"}, {12, &IAudioDevice::QueryAudioDeviceOutputEvent, "QueryAudioDeviceOutputEvent"}, {13, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioOutputDeviceName"}, {14, &IAudioDevice::ListAudioOutputDeviceName, "ListAudioOutputDeviceName"}, }; RegisterHandlers(functions); event->Signal(); } ~IAudioDevice() override { service_context.CloseEvent(event); } private: void ListAudioDeviceName(HLERequestContext& ctx) { const size_t in_count = ctx.GetWriteBufferNumElements(); std::vector out_names{}; const u32 out_count = impl->ListAudioDeviceName(out_names, in_count); std::string out{}; for (u32 i = 0; i < out_count; i++) { std::string a{}; u32 j = 0; while (out_names[i].name[j] != '\0') { a += out_names[i].name[j]; j++; } out += "\n\t" + a; } LOG_DEBUG(Service_Audio, "called.\nNames={}", out); IPC::ResponseBuilder rb{ctx, 3}; ctx.WriteBuffer(out_names); rb.Push(ResultSuccess); rb.Push(out_count); } void SetAudioDeviceOutputVolume(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const f32 volume = rp.Pop(); const auto device_name_buffer = ctx.ReadBuffer(); const std::string name = Common::StringFromBuffer(device_name_buffer); LOG_DEBUG(Service_Audio, "called. name={}, volume={}", name, volume); if (name == "AudioTvOutput") { impl->SetDeviceVolumes(volume); } IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } void GetAudioDeviceOutputVolume(HLERequestContext& ctx) { const auto device_name_buffer = ctx.ReadBuffer(); const std::string name = Common::StringFromBuffer(device_name_buffer); LOG_DEBUG(Service_Audio, "called. Name={}", name); f32 volume{1.0f}; if (name == "AudioTvOutput") { volume = impl->GetDeviceVolume(name); } IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); rb.Push(volume); } void GetActiveAudioDeviceName(HLERequestContext& ctx) { const auto write_size = ctx.GetWriteBufferSize(); std::string out_name{"AudioTvOutput"}; LOG_DEBUG(Service_Audio, "(STUBBED) called. Name={}", out_name); out_name.resize(write_size); ctx.WriteBuffer(out_name); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } void QueryAudioDeviceSystemEvent(HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "(STUBBED) called"); event->Signal(); IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(ResultSuccess); rb.PushCopyObjects(event->GetReadableEvent()); } void GetActiveChannelCount(HLERequestContext& ctx) { const auto& sink{system.AudioCore().GetOutputSink()}; u32 channel_count{sink.GetSystemChannels()}; LOG_DEBUG(Service_Audio, "(STUBBED) called. Channels={}", channel_count); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); rb.Push(channel_count); } void QueryAudioDeviceInputEvent(HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(ResultSuccess); rb.PushCopyObjects(event->GetReadableEvent()); } void QueryAudioDeviceOutputEvent(HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "called"); IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(ResultSuccess); rb.PushCopyObjects(event->GetReadableEvent()); } void ListAudioOutputDeviceName(HLERequestContext& ctx) { const size_t in_count = ctx.GetWriteBufferNumElements(); std::vector out_names{}; const u32 out_count = impl->ListAudioOutputDeviceName(out_names, in_count); std::string out{}; for (u32 i = 0; i < out_count; i++) { std::string a{}; u32 j = 0; while (out_names[i].name[j] != '\0') { a += out_names[i].name[j]; j++; } out += "\n\t" + a; } LOG_DEBUG(Service_Audio, "called.\nNames={}", out); IPC::ResponseBuilder rb{ctx, 3}; ctx.WriteBuffer(out_names); rb.Push(ResultSuccess); rb.Push(out_count); } KernelHelpers::ServiceContext service_context; std::unique_ptr impl; Kernel::KEvent* event; }; AudRenU::AudRenU(Core::System& system_) : ServiceFramework{system_, "audren:u"}, service_context{system_, "audren:u"}, impl{std::make_unique(system_)} { // clang-format off static const FunctionInfo functions[] = { {0, &AudRenU::OpenAudioRenderer, "OpenAudioRenderer"}, {1, &AudRenU::GetWorkBufferSize, "GetWorkBufferSize"}, {2, &AudRenU::GetAudioDeviceService, "GetAudioDeviceService"}, {3, nullptr, "OpenAudioRendererForManualExecution"}, {4, &AudRenU::GetAudioDeviceServiceWithRevisionInfo, "GetAudioDeviceServiceWithRevisionInfo"}, }; // clang-format on RegisterHandlers(functions); } AudRenU::~AudRenU() = default; void AudRenU::OpenAudioRenderer(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; AudioCore::AudioRendererParameterInternal params; rp.PopRaw(params); rp.Skip(1, false); auto transfer_memory_size = rp.Pop(); auto applet_resource_user_id = rp.Pop(); auto transfer_memory_handle = ctx.GetCopyHandle(0); auto process_handle = ctx.GetCopyHandle(1); if (impl->GetSessionCount() + 1 > AudioCore::MaxRendererSessions) { LOG_ERROR(Service_Audio, "Too many AudioRenderer sessions open!"); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(Audio::ResultOutOfSessions); return; } auto process{ctx.GetObjectFromHandle(process_handle).GetPointerUnsafe()}; auto transfer_memory{ctx.GetObjectFromHandle(transfer_memory_handle)}; const auto session_id{impl->GetSessionId()}; if (session_id == -1) { LOG_ERROR(Service_Audio, "Tried to open a session that's already in use!"); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(Audio::ResultOutOfSessions); return; } LOG_DEBUG(Service_Audio, "Opened new AudioRenderer session {} sessions open {}", session_id, impl->GetSessionCount()); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); rb.PushIpcInterface(system, *impl, params, transfer_memory.GetPointerUnsafe(), transfer_memory_size, process_handle, *process, applet_resource_user_id, session_id); } void AudRenU::GetWorkBufferSize(HLERequestContext& ctx) { AudioCore::AudioRendererParameterInternal params; IPC::RequestParser rp{ctx}; rp.PopRaw(params); u64 size{0}; auto result = impl->GetWorkBufferSize(params, size); std::string output_info{}; output_info += fmt::format("\tRevision {}", AudioCore::GetRevisionNum(params.revision)); output_info += fmt::format("\n\tSample Rate {}, Sample Count {}", params.sample_rate, params.sample_count); output_info += fmt::format("\n\tExecution Mode {}, Voice Drop Enabled {}", static_cast(params.execution_mode), params.voice_drop_enabled); output_info += fmt::format( "\n\tSizes: Effects {:04X}, Mixes {:04X}, Sinks {:04X}, Submixes {:04X}, Splitter Infos " "{:04X}, Splitter Destinations {:04X}, Voices {:04X}, Performance Frames {:04X} External " "Context {:04X}", params.effects, params.mixes, params.sinks, params.sub_mixes, params.splitter_infos, params.splitter_destinations, params.voices, params.perf_frames, params.external_context_size); LOG_DEBUG(Service_Audio, "called.\nInput params:\n{}\nOutput params:\n\tWorkbuffer size {:08X}", output_info, size); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(result); rb.Push(size); } void AudRenU::GetAudioDeviceService(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id = rp.Pop(); LOG_DEBUG(Service_Audio, "called. Applet resource id {}", applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); rb.PushIpcInterface(system, applet_resource_user_id, ::Common::MakeMagic('R', 'E', 'V', '1'), num_audio_devices++); } void AudRenU::OpenAudioRendererForManualExecution(HLERequestContext& ctx) { LOG_ERROR(Service_Audio, "called. Implement me!"); } void AudRenU::GetAudioDeviceServiceWithRevisionInfo(HLERequestContext& ctx) { struct Parameters { u32 revision; u64 applet_resource_user_id; }; IPC::RequestParser rp{ctx}; const auto [revision, applet_resource_user_id] = rp.PopRaw(); LOG_DEBUG(Service_Audio, "called. Revision {} Applet resource id {}", AudioCore::GetRevisionNum(revision), applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); rb.PushIpcInterface(system, applet_resource_user_id, revision, num_audio_devices++); } } // namespace Service::Audio