diff options
Diffstat (limited to 'src/core/hle/service/audio')
-rw-r--r-- | src/core/hle/service/audio/audout_u.cpp | 195 | ||||
-rw-r--r-- | src/core/hle/service/audio/audout_u.h | 14 | ||||
-rw-r--r-- | src/core/hle/service/audio/hwopus.cpp | 135 | ||||
-rw-r--r-- | src/core/hle/service/audio/hwopus.h | 1 |
4 files changed, 247 insertions, 98 deletions
diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp index 1dcd84d98..ab37c2a69 100644 --- a/src/core/hle/service/audio/audout_u.cpp +++ b/src/core/hle/service/audio/audout_u.cpp @@ -5,8 +5,7 @@ #include <array> #include <vector> #include "common/logging/log.h" -#include "core/core_timing.h" -#include "core/core_timing_util.h" +#include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/event.h" #include "core/hle/kernel/hle_ipc.h" @@ -14,17 +13,21 @@ namespace Service::Audio { -/// Switch sample rate frequency -constexpr u32 sample_rate{48000}; -/// TODO(st4rk): dynamic number of channels, as I think Switch has support -/// to more audio channels (probably when Docked I guess) -constexpr u32 audio_channels{2}; -/// TODO(st4rk): find a proper value for the audio_ticks -constexpr u64 audio_ticks{static_cast<u64>(CoreTiming::BASE_CLOCK_RATE / 500)}; +namespace ErrCodes { +enum { + ErrorUnknown = 2, + BufferCountExceeded = 8, +}; +} + +constexpr std::array<char, 10> DefaultDevice{{"DeviceOut"}}; +constexpr int DefaultSampleRate{48000}; class IAudioOut final : public ServiceFramework<IAudioOut> { public: - IAudioOut() : ServiceFramework("IAudioOut"), audio_out_state(AudioState::Stopped) { + IAudioOut(AudoutParams audio_params, AudioCore::AudioOut& audio_core) + : ServiceFramework("IAudioOut"), audio_params(audio_params), audio_core(audio_core) { + static const FunctionInfo functions[] = { {0, &IAudioOut::GetAudioOutState, "GetAudioOutState"}, {1, &IAudioOut::StartAudioOut, "StartAudioOut"}, @@ -32,66 +35,65 @@ public: {3, &IAudioOut::AppendAudioOutBufferImpl, "AppendAudioOutBuffer"}, {4, &IAudioOut::RegisterBufferEvent, "RegisterBufferEvent"}, {5, &IAudioOut::GetReleasedAudioOutBufferImpl, "GetReleasedAudioOutBuffer"}, - {6, nullptr, "ContainsAudioOutBuffer"}, + {6, &IAudioOut::ContainsAudioOutBuffer, "ContainsAudioOutBuffer"}, {7, &IAudioOut::AppendAudioOutBufferImpl, "AppendAudioOutBufferAuto"}, {8, &IAudioOut::GetReleasedAudioOutBufferImpl, "GetReleasedAudioOutBufferAuto"}, - {9, nullptr, "GetAudioOutBufferCount"}, + {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 - buffer_event = - Kernel::Event::Create(Kernel::ResetType::OneShot, "IAudioOutBufferReleasedEvent"); - - // Register event callback to update the Audio Buffer - audio_event = CoreTiming::RegisterEvent( - "IAudioOut::UpdateAudioBuffersCallback", [this](u64 userdata, int cycles_late) { - UpdateAudioBuffersCallback(); - CoreTiming::ScheduleEvent(audio_ticks - cycles_late, audio_event); - }); - - // Start the audio event - CoreTiming::ScheduleEvent(audio_ticks, audio_event); - } + buffer_event = Kernel::Event::Create(Kernel::ResetType::Sticky, "IAudioOutBufferReleased"); - ~IAudioOut() { - CoreTiming::UnscheduleEvent(audio_event, 0); + stream = audio_core.OpenStream(audio_params.sample_rate, audio_params.channel_count, + [=]() { 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<u32>(audio_out_state)); + rb.Push(static_cast<u32>(stream->IsPlaying() ? AudioState::Started : AudioState::Stopped)); } void StartAudioOut(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Audio, "(STUBBED) called"); + LOG_DEBUG(Service_Audio, "called"); + + if (stream->IsPlaying()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultCode(ErrorModule::Audio, ErrCodes::ErrorUnknown)); + return; + } - // Start audio - audio_out_state = AudioState::Started; + audio_core.StartStream(stream); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } void StopAudioOut(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Audio, "(STUBBED) called"); - - // Stop audio - audio_out_state = AudioState::Stopped; + LOG_DEBUG(Service_Audio, "called"); - queue_keys.clear(); + audio_core.StopStream(stream); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } void RegisterBufferEvent(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Audio, "(STUBBED) called"); + LOG_DEBUG(Service_Audio, "called"); IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); @@ -99,101 +101,107 @@ private: } void AppendAudioOutBufferImpl(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Audio, "(STUBBED) called"); + LOG_DEBUG(Service_Audio, "(STUBBED) called {}", ctx.Description()); IPC::RequestParser rp{ctx}; - const u64 key{rp.Pop<u64>()}; - queue_keys.insert(queue_keys.begin(), key); + 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<u64>()}; + + std::vector<u8> data(audio_buffer.buffer_size); + Memory::ReadBlock(audio_buffer.buffer, data.data(), data.size()); + + if (!audio_core.QueueBuffer(stream, tag, std::move(data))) { + 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_WARNING(Service_Audio, "(STUBBED) called"); - - // TODO(st4rk): This is how libtransistor currently implements the - // GetReleasedAudioOutBuffer, it should return the key (a VAddr) to the app and this address - // is used to know which buffer should be filled with data and send again to the service - // through AppendAudioOutBuffer. Check if this is the proper way to do it. - u64 key{0}; - - if (queue_keys.size()) { - key = queue_keys.back(); - queue_keys.pop_back(); - } + 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)}; - ctx.WriteBuffer(&key, sizeof(u64)); + std::vector<u64> tags{released_buffers}; + tags.resize(max_count); + ctx.WriteBuffer(tags); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - // TODO(st4rk): This might be the total of released buffers, needs to be verified on - // hardware - rb.Push<u32>(static_cast<u32>(queue_keys.size())); + rb.Push<u32>(static_cast<u32>(released_buffers.size())); } - void UpdateAudioBuffersCallback() { - if (audio_out_state != AudioState::Started) { - return; - } - - if (queue_keys.empty()) { - return; - } + void ContainsAudioOutBuffer(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_Audio, "called"); + IPC::RequestParser rp{ctx}; + const u64 tag{rp.Pop<u64>()}; + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(stream->ContainsBuffer(tag)); + } - buffer_event->Signal(); + void GetAudioOutBufferCount(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_Audio, "called"); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(static_cast<u32>(stream->GetQueueSize())); } - enum class AudioState : u32 { - Started, - Stopped, - }; + AudioCore::AudioOut& audio_core; + AudioCore::StreamPtr stream; - /// This is used to trigger the audio event callback that is going to read the samples from the - /// audio_buffer list and enqueue the samples using the sink (audio_core). - CoreTiming::EventType* audio_event; + AudoutParams audio_params{}; /// This is the evend handle used to check if the audio buffer was released Kernel::SharedPtr<Kernel::Event> buffer_event; - - /// (st4rk): This is just a temporary workaround for the future implementation. Libtransistor - /// uses the key as an address in the App, so we need to return when the - /// GetReleasedAudioOutBuffer_1 is called, otherwise we'll run in problems, because - /// libtransistor uses the key returned as an pointer. - std::vector<u64> queue_keys; - - AudioState audio_out_state; }; void AudOutU::ListAudioOutsImpl(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Audio, "(STUBBED) called"); + LOG_DEBUG(Service_Audio, "called"); IPC::RequestParser rp{ctx}; - constexpr std::array<char, 15> audio_interface{{"AudioInterface"}}; - ctx.WriteBuffer(audio_interface); + ctx.WriteBuffer(DefaultDevice); IPC::ResponseBuilder rb = rp.MakeBuilder(3, 0, 0); rb.Push(RESULT_SUCCESS); - // TODO(st4rk): We're currently returning only one audio interface (stringlist size). However, - // it's highly possible to have more than one interface (despite that libtransistor requires - // only one). - rb.Push<u32>(1); + rb.Push<u32>(1); // Amount of audio devices } void AudOutU::OpenAudioOutImpl(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Audio, "(STUBBED) called"); + LOG_DEBUG(Service_Audio, "called"); - if (!audio_out_interface) { - audio_out_interface = std::make_shared<IAudioOut>(); + ctx.WriteBuffer(DefaultDevice); + IPC::RequestParser rp{ctx}; + auto params{rp.PopRaw<AudoutParams>()}; + 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<IAudioOut>(std::move(params), *audio_core); IPC::ResponseBuilder rb{ctx, 6, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.Push<u32>(sample_rate); - rb.Push<u32>(audio_channels); + rb.Push<u32>(DefaultSampleRate); + rb.Push<u32>(params.channel_count); rb.Push<u32>(static_cast<u32>(PcmFormat::Int16)); - rb.Push<u32>(0); // This field is unknown + rb.Push<u32>(static_cast<u32>(AudioState::Stopped)); rb.PushIpcInterface<Audio::IAudioOut>(audio_out_interface); } @@ -203,6 +211,7 @@ AudOutU::AudOutU() : ServiceFramework("audout:u") { {2, &AudOutU::ListAudioOutsImpl, "ListAudioOutsAuto"}, {3, &AudOutU::OpenAudioOutImpl, "OpenAudioOutAuto"}}; RegisterHandlers(functions); + audio_core = std::make_unique<AudioCore::AudioOut>(); } } // namespace Service::Audio diff --git a/src/core/hle/service/audio/audout_u.h b/src/core/hle/service/audio/audout_u.h index 847d86aa6..e5c2184d5 100644 --- a/src/core/hle/service/audio/audout_u.h +++ b/src/core/hle/service/audio/audout_u.h @@ -4,6 +4,7 @@ #pragma once +#include "audio_core/audio_out.h" #include "core/hle/service/service.h" namespace Kernel { @@ -12,6 +13,18 @@ class HLERequestContext; namespace Service::Audio { +struct AudoutParams { + s32_le sample_rate; + u16_le channel_count; + INSERT_PADDING_BYTES(2); +}; +static_assert(sizeof(AudoutParams) == 0x8, "AudoutParams is an invalid size"); + +enum class AudioState : u32 { + Started, + Stopped, +}; + class IAudioOut; class AudOutU final : public ServiceFramework<AudOutU> { @@ -21,6 +34,7 @@ public: private: std::shared_ptr<IAudioOut> audio_out_interface; + std::unique_ptr<AudioCore::AudioOut> audio_core; void ListAudioOutsImpl(Kernel::HLERequestContext& ctx); void OpenAudioOutImpl(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/audio/hwopus.cpp b/src/core/hle/service/audio/hwopus.cpp index 844df382c..371cd4997 100644 --- a/src/core/hle/service/audio/hwopus.cpp +++ b/src/core/hle/service/audio/hwopus.cpp @@ -2,6 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <cstring> +#include <opus.h> #include "common/logging/log.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/hle_ipc.h" @@ -9,19 +11,142 @@ namespace Service::Audio { +struct OpusDeleter { + void operator()(void* ptr) const { + operator delete(ptr); + } +}; + +class IHardwareOpusDecoderManager final : public ServiceFramework<IHardwareOpusDecoderManager> { +public: + IHardwareOpusDecoderManager(std::unique_ptr<OpusDecoder, OpusDeleter> decoder, u32 sample_rate, + u32 channel_count) + : ServiceFramework("IHardwareOpusDecoderManager"), decoder(std::move(decoder)), + sample_rate(sample_rate), channel_count(channel_count) { + static const FunctionInfo functions[] = { + {0, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleaved"}, + {1, nullptr, "SetContext"}, + {2, nullptr, "DecodeInterleavedForMultiStream"}, + {3, nullptr, "SetContextForMultiStream"}, + {4, nullptr, "Unknown4"}, + {5, nullptr, "Unknown5"}, + {6, nullptr, "Unknown6"}, + {7, nullptr, "Unknown7"}, + }; + RegisterHandlers(functions); + } + +private: + void DecodeInterleaved(Kernel::HLERequestContext& ctx) { + u32 consumed = 0; + u32 sample_count = 0; + std::vector<opus_int16> samples(ctx.GetWriteBufferSize() / sizeof(opus_int16)); + if (!Decoder_DecodeInterleaved(consumed, sample_count, ctx.ReadBuffer(), samples)) { + IPC::ResponseBuilder rb{ctx, 2}; + // TODO(ogniK): Use correct error code + rb.Push(ResultCode(-1)); + return; + } + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(consumed); + rb.Push<u32>(sample_count); + ctx.WriteBuffer(samples.data(), samples.size() * sizeof(s16)); + } + + bool Decoder_DecodeInterleaved(u32& consumed, u32& sample_count, const std::vector<u8>& input, + std::vector<opus_int16>& output) { + size_t raw_output_sz = output.size() * sizeof(opus_int16); + if (sizeof(OpusHeader) > input.size()) + return false; + OpusHeader hdr{}; + std::memcpy(&hdr, input.data(), sizeof(OpusHeader)); + if (sizeof(OpusHeader) + static_cast<u32>(hdr.sz) > input.size()) { + return false; + } + auto frame = input.data() + sizeof(OpusHeader); + auto decoded_sample_count = opus_packet_get_nb_samples( + frame, static_cast<opus_int32>(input.size() - sizeof(OpusHeader)), + static_cast<opus_int32>(sample_rate)); + if (decoded_sample_count * channel_count * sizeof(u16) > raw_output_sz) + return false; + auto out_sample_count = + opus_decode(decoder.get(), frame, hdr.sz, output.data(), + (static_cast<int>(raw_output_sz / sizeof(s16) / channel_count)), 0); + if (out_sample_count < 0) + return false; + sample_count = out_sample_count; + consumed = static_cast<u32>(sizeof(OpusHeader) + hdr.sz); + return true; + } + + struct OpusHeader { + u32_be sz; // Needs to be BE for some odd reason + INSERT_PADDING_WORDS(1); + }; + static_assert(sizeof(OpusHeader) == 0x8, "OpusHeader is an invalid size"); + + std::unique_ptr<OpusDecoder, OpusDeleter> decoder; + u32 sample_rate; + u32 channel_count; +}; + +static size_t WorkerBufferSize(u32 channel_count) { + ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); + return opus_decoder_get_size(static_cast<int>(channel_count)); +} + void HwOpus::GetWorkBufferSize(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Audio, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + auto sample_rate = rp.Pop<u32>(); + auto channel_count = rp.Pop<u32>(); + ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || + sample_rate == 12000 || sample_rate == 8000, + "Invalid sample rate"); + ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); + u32 worker_buffer_sz = static_cast<u32>(WorkerBufferSize(channel_count)); + LOG_DEBUG(Audio, "called worker_buffer_sz={}", worker_buffer_sz); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push<u32>(0x4000); + rb.Push<u32>(worker_buffer_sz); +} + +void HwOpus::OpenOpusDecoder(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + auto sample_rate = rp.Pop<u32>(); + auto channel_count = rp.Pop<u32>(); + auto buffer_sz = rp.Pop<u32>(); + LOG_DEBUG(Audio, "called sample_rate={}, channel_count={}, buffer_size={}", sample_rate, + channel_count, buffer_sz); + ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || + sample_rate == 12000 || sample_rate == 8000, + "Invalid sample rate"); + ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); + + size_t worker_sz = WorkerBufferSize(channel_count); + ASSERT_MSG(buffer_sz < worker_sz, "Worker buffer too large"); + std::unique_ptr<OpusDecoder, OpusDeleter> decoder{ + static_cast<OpusDecoder*>(operator new(worker_sz))}; + if (opus_decoder_init(decoder.get(), sample_rate, channel_count)) { + IPC::ResponseBuilder rb{ctx, 2}; + // TODO(ogniK): Use correct error code + rb.Push(ResultCode(-1)); + return; + } + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IHardwareOpusDecoderManager>(std::move(decoder), sample_rate, + channel_count); } HwOpus::HwOpus() : ServiceFramework("hwopus") { static const FunctionInfo functions[] = { - {0, nullptr, "Initialize"}, + {0, &HwOpus::OpenOpusDecoder, "OpenOpusDecoder"}, {1, &HwOpus::GetWorkBufferSize, "GetWorkBufferSize"}, - {2, nullptr, "InitializeMultiStream"}, - {3, nullptr, "GetWorkBufferSizeMultiStream"}, + {2, nullptr, "OpenOpusDecoderForMultiStream"}, + {3, nullptr, "GetWorkBufferSizeForMultiStream"}, }; RegisterHandlers(functions); } diff --git a/src/core/hle/service/audio/hwopus.h b/src/core/hle/service/audio/hwopus.h index 090b8c825..5258d59f3 100644 --- a/src/core/hle/service/audio/hwopus.h +++ b/src/core/hle/service/audio/hwopus.h @@ -14,6 +14,7 @@ public: ~HwOpus() = default; private: + void OpenOpusDecoder(Kernel::HLERequestContext& ctx); void GetWorkBufferSize(Kernel::HLERequestContext& ctx); }; |