// Copyright 2018 yuzu emulator team // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #include #include #include "audio_core/codec.h" #include "common/logging/log.h" #include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/event.h" #include "core/hle/kernel/hle_ipc.h" #include "core/hle/service/audio/audout_u.h" namespace Service::Audio { namespace ErrCodes { enum { ErrorUnknown = 2, BufferCountExceeded = 8, }; } constexpr std::array DefaultDevice{{"DeviceOut"}}; constexpr int DefaultSampleRate{48000}; class IAudioOut final : public ServiceFramework { public: IAudioOut(AudoutParams audio_params, AudioCore::AudioOut& audio_core) : ServiceFramework("IAudioOut"), audio_core(audio_core), audio_params(audio_params) { static const FunctionInfo functions[] = { {0, &IAudioOut::GetAudioOutState, "GetAudioOutState"}, {1, &IAudioOut::StartAudioOut, "StartAudioOut"}, {2, &IAudioOut::StopAudioOut, "StopAudioOut"}, {3, &IAudioOut::AppendAudioOutBufferImpl, "AppendAudioOutBuffer"}, {4, &IAudioOut::RegisterBufferEvent, "RegisterBufferEvent"}, {5, &IAudioOut::GetReleasedAudioOutBufferImpl, "GetReleasedAudioOutBuffer"}, {6, &IAudioOut::ContainsAudioOutBuffer, "ContainsAudioOutBuffer"}, {7, &IAudioOut::AppendAudioOutBufferImpl, "AppendAudioOutBufferAuto"}, {8, &IAudioOut::GetReleasedAudioOutBufferImpl, "GetReleasedAudioOutBufferAuto"}, {9, &IAudioOut::GetAudioOutBufferCount, "GetAudioOutBufferCount"}, {10, nullptr, "GetAudioOutPlayedSampleCount"}, {11, nullptr, "FlushAudioOutBuffers"}, }; RegisterHandlers(functions); // This is the event handle used to check if the audio buffer was released auto& kernel = Core::System::GetInstance().Kernel(); buffer_event = Kernel::Event::Create(kernel, Kernel::ResetType::Sticky, "IAudioOutBufferReleased"); stream = audio_core.OpenStream(audio_params.sample_rate, audio_params.channel_count, "IAudioOut", [=]() { buffer_event->Signal(); }); } private: struct AudioBuffer { u64_le next; u64_le buffer; u64_le buffer_capacity; u64_le buffer_size; u64_le offset; }; static_assert(sizeof(AudioBuffer) == 0x28, "AudioBuffer is an invalid size"); void GetAudioOutState(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "called"); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push(static_cast(stream->IsPlaying() ? AudioState::Started : AudioState::Stopped)); } void StartAudioOut(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "called"); if (stream->IsPlaying()) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultCode(ErrorModule::Audio, ErrCodes::ErrorUnknown)); return; } audio_core.StartStream(stream); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } void StopAudioOut(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "called"); audio_core.StopStream(stream); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } void RegisterBufferEvent(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "called"); IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); rb.PushCopyObjects(buffer_event); } void AppendAudioOutBufferImpl(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "(STUBBED) called {}", ctx.Description()); IPC::RequestParser rp{ctx}; const auto& input_buffer{ctx.ReadBuffer()}; ASSERT_MSG(input_buffer.size() == sizeof(AudioBuffer), "AudioBuffer input is an invalid size!"); AudioBuffer audio_buffer{}; std::memcpy(&audio_buffer, input_buffer.data(), sizeof(AudioBuffer)); const u64 tag{rp.Pop()}; std::vector samples(audio_buffer.buffer_size / sizeof(s16)); Memory::ReadBlock(audio_buffer.buffer, samples.data(), audio_buffer.buffer_size); if (!audio_core.QueueBuffer(stream, tag, std::move(samples))) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultCode(ErrorModule::Audio, ErrCodes::BufferCountExceeded)); } IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } void GetReleasedAudioOutBufferImpl(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "called {}", ctx.Description()); IPC::RequestParser rp{ctx}; const u64 max_count{ctx.GetWriteBufferSize() / sizeof(u64)}; const auto released_buffers{audio_core.GetTagsAndReleaseBuffers(stream, max_count)}; std::vector tags{released_buffers}; tags.resize(max_count); ctx.WriteBuffer(tags); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push(static_cast(released_buffers.size())); } void ContainsAudioOutBuffer(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "called"); IPC::RequestParser rp{ctx}; const u64 tag{rp.Pop()}; IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push(stream->ContainsBuffer(tag)); } void GetAudioOutBufferCount(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "called"); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push(static_cast(stream->GetQueueSize())); } AudioCore::AudioOut& audio_core; AudioCore::StreamPtr stream; AudoutParams audio_params{}; /// This is the evend handle used to check if the audio buffer was released Kernel::SharedPtr buffer_event; }; void AudOutU::ListAudioOutsImpl(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "called"); IPC::RequestParser rp{ctx}; ctx.WriteBuffer(DefaultDevice); IPC::ResponseBuilder rb = rp.MakeBuilder(3, 0, 0); rb.Push(RESULT_SUCCESS); rb.Push(1); // Amount of audio devices } void AudOutU::OpenAudioOutImpl(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "called"); ctx.WriteBuffer(DefaultDevice); IPC::RequestParser rp{ctx}; auto params{rp.PopRaw()}; if (params.channel_count <= 2) { // Mono does not exist for audout params.channel_count = 2; } else { params.channel_count = 6; } if (!params.sample_rate) { params.sample_rate = DefaultSampleRate; } // TODO(bunnei): Support more than one IAudioOut interface. When we add this, ListAudioOutsImpl // will likely need to be updated as well. ASSERT_MSG(!audio_out_interface, "Unimplemented"); audio_out_interface = std::make_shared(params, *audio_core); IPC::ResponseBuilder rb{ctx, 6, 0, 1}; rb.Push(RESULT_SUCCESS); rb.Push(DefaultSampleRate); rb.Push(params.channel_count); rb.Push(static_cast(AudioCore::Codec::PcmFormat::Int16)); rb.Push(static_cast(AudioState::Stopped)); rb.PushIpcInterface(audio_out_interface); } AudOutU::AudOutU() : ServiceFramework("audout:u") { static const FunctionInfo functions[] = {{0, &AudOutU::ListAudioOutsImpl, "ListAudioOuts"}, {1, &AudOutU::OpenAudioOutImpl, "OpenAudioOut"}, {2, &AudOutU::ListAudioOutsImpl, "ListAudioOutsAuto"}, {3, &AudOutU::OpenAudioOutImpl, "OpenAudioOutAuto"}}; RegisterHandlers(functions); audio_core = std::make_unique(); } } // namespace Service::Audio