diff --git a/src/audio_core/hle/decoder.h b/src/audio_core/hle/decoder.h index 6c938a9fc..c34e1ab28 100644 --- a/src/audio_core/hle/decoder.h +++ b/src/audio_core/hle/decoder.h @@ -123,7 +123,6 @@ struct BinaryMessage { EncodeAACResponse encode_aac_response; }; }; - static_assert(sizeof(BinaryMessage) == 32, "Unexpected struct size for BinaryMessage"); enum_le GetSampleRateEnum(u32 sample_rate); diff --git a/src/audio_core/lle/lle.cpp b/src/audio_core/lle/lle.cpp index 5439841f2..f644e7541 100644 --- a/src/audio_core/lle/lle.cpp +++ b/src/audio_core/lle/lle.cpp @@ -468,6 +468,14 @@ void DspLle::SetServiceToInterrupt(std::weak_ptr dsp) { impl->teakra.SetSemaphoreHandler([ProcessPipeEvent]() { ProcessPipeEvent(false); }); } +void DspLle::SetSemaphoreHandler(std::function handler) { + impl->teakra.SetSemaphoreHandler(handler); +} + +void DspLle::SetRecvDataHandler(u8 index, std::function handler) { + impl->teakra.SetRecvDataHandler(index, handler); +} + void DspLle::LoadComponent(const std::vector& buffer) { impl->LoadComponent(buffer); } diff --git a/src/audio_core/lle/lle.h b/src/audio_core/lle/lle.h index c2ac175a5..e3c3ab82b 100644 --- a/src/audio_core/lle/lle.h +++ b/src/audio_core/lle/lle.h @@ -28,6 +28,9 @@ public: void SetServiceToInterrupt(std::weak_ptr dsp) override; + void SetSemaphoreHandler(std::function handler); + void SetRecvDataHandler(u8 index, std::function handler); + void LoadComponent(const std::vector& buffer) override; void UnloadComponent() override; diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 9801f6a09..5b90fc8e8 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -11,6 +11,8 @@ add_executable(tests core/memory/memory.cpp core/memory/vm_manager.cpp precompiled_headers.h + audio_core/hle/hle.cpp + audio_core/lle/lle.cpp audio_core/audio_fixures.h audio_core/decoder_tests.cpp video_core/shader/shader_jit_x64_compiler.cpp diff --git a/src/tests/audio_core/hle/hle.cpp b/src/tests/audio_core/hle/hle.cpp new file mode 100644 index 000000000..e70008917 --- /dev/null +++ b/src/tests/audio_core/hle/hle.cpp @@ -0,0 +1,143 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include "audio_core/hle/decoder.h" +#include "audio_core/hle/hle.h" +#include "audio_core/lle/lle.h" +#include "common/common_paths.h" +#include "core/core_timing.h" +#include "core/memory.h" + +TEST_CASE("DSP LLE vs HLE", "[audio_core][hle]") { + Memory::MemorySystem hle_memory; + Core::Timing hle_core_timing(1, 100); + + Memory::MemorySystem lle_memory; + Core::Timing lle_core_timing(1, 100); + + AudioCore::DspHle hle(hle_memory, hle_core_timing); + AudioCore::DspLle lle(lle_memory, lle_core_timing, true); + + // Initialiase LLE + { + FileUtil::SetUserPath(); + // see tests/audio_core/lle/lle.cpp for details on dspaudio.cdc + std::string firm_filepath = + FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) + "3ds" DIR_SEP "dspaudio.cdc"; + + if (!FileUtil::Exists(firm_filepath)) { + SKIP("Test requires dspaudio.cdc"); + } + + FileUtil::IOFile firm_file(firm_filepath, "rb"); + + std::vector firm_file_buf(firm_file.GetSize()); + firm_file.ReadArray(firm_file_buf.data(), firm_file_buf.size()); + lle.LoadComponent(firm_file_buf); + lle.SetSemaphoreHandler([&lle]() { + u16 slot = lle.RecvData(2); + u16 side = slot % 2; + u16 pipe = slot / 2; + fmt::print("SetSemaphoreHandler slot={}\n", slot); + if (pipe > 15) + return; + if (side != 0) + return; + if (pipe == 0) { + // pipe 0 is for debug. 3DS automatically drains this pipe and discards the + // data + lle.PipeRead(static_cast(pipe), + lle.GetPipeReadableSize(static_cast(pipe))); + } + }); + lle.SetRecvDataHandler(0, []() { fmt::print("SetRecvDataHandler 0\n"); }); + lle.SetRecvDataHandler(1, []() { fmt::print("SetRecvDataHandler 1\n"); }); + lle.SetRecvDataHandler(2, []() { fmt::print("SetRecvDataHandler 2\n"); }); + } + + SECTION("Initialise Audio Pipe") { + std::vector buffer(4, 0); + buffer[0] = 0; + + // LLE + { + lle.PipeWrite(AudioCore::DspPipe::Audio, buffer); + lle.SetSemaphore(0x4000); + + // todo: wait for interrupt + do { + lle_core_timing.GetTimer(0)->AddTicks(lle_core_timing.GetTimer(0)->GetDowncount()); + lle_core_timing.GetTimer(0)->Advance(); + lle_core_timing.GetTimer(0)->SetNextSlice(); + } while (lle.GetPipeReadableSize(AudioCore::DspPipe::Audio) == 0); + + REQUIRE(lle.GetPipeReadableSize(AudioCore::DspPipe::Audio) >= 32); + } + std::vector lle_read_buffer; + lle_read_buffer = lle.PipeRead(AudioCore::DspPipe::Audio, 2); + u16 lle_size; + memcpy(&lle_size, lle_read_buffer.data(), sizeof(lle_size)); + lle_read_buffer = lle.PipeRead(AudioCore::DspPipe::Audio, lle_size * 2); + + // HLE + { + hle.PipeWrite(AudioCore::DspPipe::Audio, buffer); + REQUIRE(hle.GetPipeReadableSize(AudioCore::DspPipe::Audio) >= 32); + } + std::vector hle_read_buffer(32); + hle_read_buffer = hle.PipeRead(AudioCore::DspPipe::Audio, 2); + u16 hle_size; + memcpy(&hle_size, hle_read_buffer.data(), sizeof(hle_size)); + hle_read_buffer = hle.PipeRead(AudioCore::DspPipe::Audio, hle_size * 2); + + REQUIRE(hle_size == lle_size); + REQUIRE(hle_read_buffer == lle_read_buffer); + } + + SECTION("Initialise Binary Pipe") { + std::vector buffer(32, 0); + AudioCore::HLE::BinaryMessage& request = + *reinterpret_cast(buffer.data()); + + request.header.codec = AudioCore::HLE::DecoderCodec::DecodeAAC; + request.header.cmd = AudioCore::HLE::DecoderCommand::Init; + + // Values used by Pokemon X + request.header.result = static_cast(3); + request.decode_aac_init.unknown1 = 1; + request.decode_aac_init.unknown2 = 0xFFFF'FFFF; + request.decode_aac_init.unknown3 = 1; + request.decode_aac_init.unknown4 = 0; + request.decode_aac_init.unknown5 = 1; + request.decode_aac_init.unknown6 = 0x20; + + // LLE + lle.PipeWrite(AudioCore::DspPipe::Binary, buffer); + lle.SetSemaphore(0x4000); + + // todo: wait for interrupt + do { + lle_core_timing.GetTimer(0)->AddTicks(lle_core_timing.GetTimer(0)->GetDowncount()); + lle_core_timing.GetTimer(0)->Advance(); + lle_core_timing.GetTimer(0)->SetNextSlice(); + } while (lle.GetPipeReadableSize(AudioCore::DspPipe::Binary) == 0); + + REQUIRE(lle.GetPipeReadableSize(AudioCore::DspPipe::Binary) >= 32); + + std::vector lle_read_buffer = lle.PipeRead(AudioCore::DspPipe::Binary, 32); + AudioCore::HLE::BinaryMessage& resp = + *reinterpret_cast(lle_read_buffer.data()); + CHECK(resp.header.result == AudioCore::HLE::ResultStatus::Success); + + // HLE + { + hle.PipeWrite(AudioCore::DspPipe::Binary, buffer); + REQUIRE(hle.GetPipeReadableSize(AudioCore::DspPipe::Binary) >= 32); + } + std::vector hle_read_buffer = hle.PipeRead(AudioCore::DspPipe::Binary, 32); + + REQUIRE(hle_read_buffer == lle_read_buffer); + } +} diff --git a/src/tests/audio_core/lle/lle.cpp b/src/tests/audio_core/lle/lle.cpp new file mode 100644 index 000000000..9396bb6e1 --- /dev/null +++ b/src/tests/audio_core/lle/lle.cpp @@ -0,0 +1,110 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include "audio_core/hle/decoder.h" +#include "audio_core/lle/lle.h" +#include "common/common_paths.h" +#include "core/core_timing.h" +#include "core/memory.h" + +TEST_CASE("DSP LLE Sanity", "[audio_core][lle]") { + Memory::MemorySystem memory; + Core::Timing core_timing(1, 100); + + AudioCore::DspLle lle(memory, core_timing, true); + { + FileUtil::SetUserPath(); + // dspaudio.cdc can be dumped from Pokemon X & Y, It can be found in the romfs at + // "rom:/sound/dspaudio.cdc". + // One could also extract the firmware from the 3DS sound app using a modified version of + // https://github.com/zoogie/DSP1. + std::string firm_filepath = + FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) + "3ds" DIR_SEP "dspaudio.cdc"; + + if (!FileUtil::Exists(firm_filepath)) { + SKIP("Test requires dspaudio.cdc"); + } + + FileUtil::IOFile firm_file(firm_filepath, "rb"); + + std::vector firm_file_buf(firm_file.GetSize()); + firm_file.ReadArray(firm_file_buf.data(), firm_file_buf.size()); + lle.LoadComponent(firm_file_buf); + } + lle.SetSemaphoreHandler([&lle]() { + u16 slot = lle.RecvData(2); + u16 side = slot % 2; + u16 pipe = slot / 2; + fmt::print("SetSemaphoreHandler slot={}\n", slot); + if (pipe > 15) + return; + if (side != 0) + return; + if (pipe == 0) { + // pipe 0 is for debug. 3DS automatically drains this pipe and discards the + // data + lle.PipeRead(static_cast(pipe), + lle.GetPipeReadableSize(static_cast(pipe))); + } + }); + lle.SetRecvDataHandler(0, []() { fmt::print("SetRecvDataHandler 0\n"); }); + lle.SetRecvDataHandler(1, []() { fmt::print("SetRecvDataHandler 1\n"); }); + lle.SetRecvDataHandler(2, []() { fmt::print("SetRecvDataHandler 2\n"); }); + SECTION("Initialise Audio Pipe") { + std::vector buffer(4, 0); + buffer[0] = 0; + + lle.PipeWrite(AudioCore::DspPipe::Audio, buffer); + lle.SetSemaphore(0x4000); + + // todo: wait for interrupt + do { + core_timing.GetTimer(0)->AddTicks(core_timing.GetTimer(0)->GetDowncount()); + core_timing.GetTimer(0)->Advance(); + core_timing.GetTimer(0)->SetNextSlice(); + } while (lle.GetPipeReadableSize(AudioCore::DspPipe::Audio) == 0); + + REQUIRE(lle.GetPipeReadableSize(AudioCore::DspPipe::Audio) >= 32); + + buffer = lle.PipeRead(AudioCore::DspPipe::Audio, 2); + u16 size; + memcpy(&size, buffer.data(), sizeof(size)); + // see AudioCore::DspHle::Impl::AudioPipeWriteStructAddresses() + REQUIRE(size * 2 == 30); + } + SECTION("Initialise EncodeAAC - Binary Pipe") { + std::vector buffer(32, 0); + AudioCore::HLE::BinaryMessage& request = + *reinterpret_cast(buffer.data()); + + request.header.codec = AudioCore::HLE::DecoderCodec::EncodeAAC; + request.header.cmd = AudioCore::HLE::DecoderCommand::Init; + + // Values used by the 3DS sound app. + request.encode_aac_init.unknown1 = 1; + request.encode_aac_init.sample_rate = static_cast(5); + request.encode_aac_init.unknown3 = 2; + request.encode_aac_init.unknown4 = 0; + request.encode_aac_init.unknown5 = rand(); + request.encode_aac_init.unknown6 = rand(); + lle.PipeWrite(AudioCore::DspPipe::Binary, buffer); + lle.SetSemaphore(0x4000); + + // todo: wait for interrupt + do { + core_timing.GetTimer(0)->AddTicks(core_timing.GetTimer(0)->GetDowncount()); + core_timing.GetTimer(0)->Advance(); + core_timing.GetTimer(0)->SetNextSlice(); + } while (lle.GetPipeReadableSize(AudioCore::DspPipe::Binary) == 0); + + REQUIRE(lle.GetPipeReadableSize(AudioCore::DspPipe::Binary) >= 32); + + buffer = lle.PipeRead(AudioCore::DspPipe::Binary, 32); + AudioCore::HLE::BinaryMessage& resp = + *reinterpret_cast(buffer.data()); + REQUIRE(resp.header.result == AudioCore::HLE::ResultStatus::Success); + REQUIRE(resp.data == request.data); + } +}