// Copyright 2017 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #include #include #include #include #include #include "audio_core/audio_types.h" #include "audio_core/hle/aac_decoder.h" #include "audio_core/hle/common.h" #include "audio_core/hle/decoder.h" #include "audio_core/hle/hle.h" #include "audio_core/hle/mixers.h" #include "audio_core/hle/shared_memory.h" #include "audio_core/hle/source.h" #include "audio_core/sink.h" #include "common/archives.h" #include "common/assert.h" #include "common/common_types.h" #include "common/hash.h" #include "common/logging/log.h" #include "core/core.h" #include "core/core_timing.h" using InterruptType = Service::DSP::InterruptType; namespace AudioCore { DspHle::DspHle(Core::System& system) : DspHle(system, system.Memory(), system.CoreTiming()) {} template void DspHle::serialize(Archive& ar, const unsigned int) { ar& boost::serialization::base_object(*this); ar&* impl.get(); } SERIALIZE_IMPL(DspHle) // The value below is the "perfect" mathematical ratio of ARM11 cycles per audio frame, samples per // frame * teaklite cycles per sample * 2 ARM11 cycles/teaklite cycle // (160 * 4096 * 2) = (1310720) // // This value has been verified against a rough hardware test with hardware and LLE static constexpr u64 audio_frame_ticks = samples_per_frame * 4096 * 2ull; ///< Units: ARM11 cycles struct DspHle::Impl final { public: explicit Impl(DspHle& parent, Memory::MemorySystem& memory, Core::Timing& timing); ~Impl(); DspState GetDspState() const; u16 RecvData(u32 register_number); bool RecvDataIsReady(u32 register_number) const; std::vector PipeRead(DspPipe pipe_number, std::size_t length); std::size_t GetPipeReadableSize(DspPipe pipe_number) const; void PipeWrite(DspPipe pipe_number, std::span buffer); std::array& GetDspMemory(); void SetInterruptHandler( std::function handler); private: void ResetPipes(); void WriteU16(DspPipe pipe_number, u16 value); void AudioPipeWriteStructAddresses(); std::size_t CurrentRegionIndex() const; HLE::SharedMemory& ReadRegion(); HLE::SharedMemory& WriteRegion(); StereoFrame16 GenerateCurrentFrame(); bool Tick(); void AudioTickCallback(s64 cycles_late); DspState dsp_state = DspState::Off; std::array, num_dsp_pipe> pipe_data{}; HLE::DspMemory dsp_memory; std::array sources{{ HLE::Source(0), HLE::Source(1), HLE::Source(2), HLE::Source(3), HLE::Source(4), HLE::Source(5), HLE::Source(6), HLE::Source(7), HLE::Source(8), HLE::Source(9), HLE::Source(10), HLE::Source(11), HLE::Source(12), HLE::Source(13), HLE::Source(14), HLE::Source(15), HLE::Source(16), HLE::Source(17), HLE::Source(18), HLE::Source(19), HLE::Source(20), HLE::Source(21), HLE::Source(22), HLE::Source(23), }}; HLE::Mixers mixers{}; DspHle& parent; Core::Timing& core_timing; Core::TimingEventType* tick_event{}; std::unique_ptr aac_decoder{}; std::function interrupt_handler{}; template void serialize(Archive& ar, const unsigned int) { ar & dsp_state; ar & pipe_data; ar & dsp_memory.raw_memory; ar & sources; ar & mixers; // interrupt_handler is reregistered when loading state from DSP_DSP } friend class boost::serialization::access; }; DspHle::Impl::Impl(DspHle& parent_, Memory::MemorySystem& memory, Core::Timing& timing) : parent(parent_), core_timing(timing) { dsp_memory.raw_memory.fill(0); for (auto& source : sources) { source.SetMemory(memory); } aac_decoder = std::make_unique(memory); tick_event = core_timing.RegisterEvent("AudioCore::DspHle::tick_event", [this](u64, s64 cycles_late) { this->AudioTickCallback(cycles_late); }); core_timing.ScheduleEvent(audio_frame_ticks, tick_event); } DspHle::Impl::~Impl() { core_timing.UnscheduleEvent(tick_event, 0); } DspState DspHle::Impl::GetDspState() const { return dsp_state; } u16 DspHle::Impl::RecvData(u32 register_number) { ASSERT_MSG(register_number == 0, "Unknown register_number {}", register_number); // Application reads this after requesting DSP shutdown, to verify the DSP has indeed shutdown // or slept. switch (GetDspState()) { case AudioCore::DspState::On: return 0; case AudioCore::DspState::Off: case AudioCore::DspState::Sleeping: return 1; default: UNREACHABLE(); break; } } bool DspHle::Impl::RecvDataIsReady(u32 register_number) const { ASSERT_MSG(register_number == 0, "Unknown register_number {}", register_number); return true; } std::vector DspHle::Impl::PipeRead(DspPipe pipe_number, std::size_t length) { const std::size_t pipe_index = static_cast(pipe_number); if (pipe_index >= num_dsp_pipe) { LOG_ERROR(Audio_DSP, "pipe_number = {} invalid", pipe_index); return {}; } if (length > UINT16_MAX) { // Can only read at most UINT16_MAX from the pipe LOG_ERROR(Audio_DSP, "length of {} greater than max of {}", length, UINT16_MAX); return {}; } std::vector& data = pipe_data[pipe_index]; if (length > data.size()) { LOG_WARNING( Audio_DSP, "pipe_number = {} is out of data, application requested read of {} but {} remain", pipe_index, length, data.size()); length = static_cast(data.size()); } if (length == 0) return {}; std::vector ret(data.begin(), data.begin() + length); data.erase(data.begin(), data.begin() + length); return ret; } size_t DspHle::Impl::GetPipeReadableSize(DspPipe pipe_number) const { const std::size_t pipe_index = static_cast(pipe_number); if (pipe_index >= num_dsp_pipe) { LOG_ERROR(Audio_DSP, "pipe_number = {} invalid", pipe_index); return 0; } return pipe_data[pipe_index].size(); } void DspHle::Impl::PipeWrite(DspPipe pipe_number, std::span buffer) { switch (pipe_number) { case DspPipe::Audio: { if (buffer.size() != 4) { LOG_ERROR(Audio_DSP, "DspPipe::Audio: Unexpected buffer length {} was written", buffer.size()); return; } enum class StateChange { Initialize = 0, Shutdown = 1, Wakeup = 2, Sleep = 3, }; // The difference between Initialize and Wakeup is that Input state is maintained // when sleeping but isn't when turning it off and on again. (TODO: Implement this.) // Waking up from sleep garbles some of the structs in the memory region. (TODO: // Implement this.) Applications store away the state of these structs before // sleeping and reset it back after wakeup on behalf of the DSP. switch (static_cast(buffer[0])) { case StateChange::Initialize: LOG_INFO(Audio_DSP, "Application has requested initialization of DSP hardware"); ResetPipes(); AudioPipeWriteStructAddresses(); dsp_state = DspState::On; break; case StateChange::Shutdown: LOG_INFO(Audio_DSP, "Application has requested shutdown of DSP hardware"); dsp_state = DspState::Off; break; case StateChange::Wakeup: LOG_INFO(Audio_DSP, "Application has requested wakeup of DSP hardware"); ResetPipes(); AudioPipeWriteStructAddresses(); dsp_state = DspState::On; break; case StateChange::Sleep: LOG_INFO(Audio_DSP, "Application has requested sleep of DSP hardware"); UNIMPLEMENTED(); AudioPipeWriteStructAddresses(); dsp_state = DspState::Sleeping; break; default: LOG_ERROR(Audio_DSP, "Application has requested unknown state transition of DSP hardware {}", buffer[0]); dsp_state = DspState::Off; break; } return; } case DspPipe::Binary: { // TODO(B3N30): Make this async, and signal the interrupt HLE::BinaryMessage request{}; if (sizeof(request) != buffer.size()) { LOG_CRITICAL(Audio_DSP, "got binary pipe with wrong size {}", buffer.size()); UNIMPLEMENTED(); return; } std::memcpy(&request, buffer.data(), buffer.size()); if (request.header.codec != HLE::DecoderCodec::DecodeAAC) { LOG_CRITICAL(Audio_DSP, "got unknown codec {}", static_cast(request.header.codec)); UNIMPLEMENTED(); return; } const HLE::BinaryMessage response = aac_decoder->ProcessRequest(request); pipe_data[static_cast(pipe_number)].resize(sizeof(response)); std::memcpy(pipe_data[static_cast(pipe_number)].data(), &response, sizeof(response)); interrupt_handler(InterruptType::Pipe, DspPipe::Binary); break; } default: LOG_CRITICAL(Audio_DSP, "pipe_number = {} unimplemented", static_cast(pipe_number)); UNIMPLEMENTED(); return; } } std::array& DspHle::Impl::GetDspMemory() { return dsp_memory.raw_memory; } void DspHle::Impl::SetInterruptHandler( std::function handler) { interrupt_handler = handler; } void DspHle::Impl::ResetPipes() { for (auto& data : pipe_data) { data.clear(); } dsp_state = DspState::Off; } void DspHle::Impl::WriteU16(DspPipe pipe_number, u16 value) { const std::size_t pipe_index = static_cast(pipe_number); std::vector& data = pipe_data.at(pipe_index); // Little endian data.emplace_back(value & 0xFF); data.emplace_back(value >> 8); } void DspHle::Impl::AudioPipeWriteStructAddresses() { // These struct addresses are DSP dram addresses. // See also: DSP_DSP::ConvertProcessAddressFromDspDram static const std::array struct_addresses = { 0x8000 + offsetof(HLE::SharedMemory, frame_counter) / 2, 0x8000 + offsetof(HLE::SharedMemory, source_configurations) / 2, 0x8000 + offsetof(HLE::SharedMemory, source_statuses) / 2, 0x8000 + offsetof(HLE::SharedMemory, adpcm_coefficients) / 2, 0x8000 + offsetof(HLE::SharedMemory, dsp_configuration) / 2, 0x8000 + offsetof(HLE::SharedMemory, dsp_status) / 2, 0x8000 + offsetof(HLE::SharedMemory, final_samples) / 2, 0x8000 + offsetof(HLE::SharedMemory, intermediate_mix_samples) / 2, 0x8000 + offsetof(HLE::SharedMemory, compressor) / 2, 0x8000 + offsetof(HLE::SharedMemory, dsp_debug) / 2, 0x8000 + offsetof(HLE::SharedMemory, unknown10) / 2, 0x8000 + offsetof(HLE::SharedMemory, unknown11) / 2, 0x8000 + offsetof(HLE::SharedMemory, unknown12) / 2, 0x8000 + offsetof(HLE::SharedMemory, unknown13) / 2, 0x8000 + offsetof(HLE::SharedMemory, unknown14) / 2, }; // Begin with a u16 denoting the number of structs. WriteU16(DspPipe::Audio, static_cast(struct_addresses.size())); // Then write the struct addresses. for (u16 addr : struct_addresses) { WriteU16(DspPipe::Audio, addr); } // Signal that we have data on this pipe. interrupt_handler(InterruptType::Pipe, DspPipe::Audio); } size_t DspHle::Impl::CurrentRegionIndex() const { // The region with the higher frame counter is chosen unless there is wraparound. // This function only returns a 0 or 1. const u16 frame_counter_0 = dsp_memory.region_0.frame_counter; const u16 frame_counter_1 = dsp_memory.region_1.frame_counter; if (frame_counter_0 == 0xFFFFu && frame_counter_1 != 0xFFFEu) { // Wraparound has occurred. return 1; } if (frame_counter_1 == 0xFFFFu && frame_counter_0 != 0xFFFEu) { // Wraparound has occurred. return 0; } return (frame_counter_0 > frame_counter_1) ? 0 : 1; } HLE::SharedMemory& DspHle::Impl::ReadRegion() { return CurrentRegionIndex() == 0 ? dsp_memory.region_0 : dsp_memory.region_1; } HLE::SharedMemory& DspHle::Impl::WriteRegion() { return CurrentRegionIndex() != 0 ? dsp_memory.region_0 : dsp_memory.region_1; } StereoFrame16 DspHle::Impl::GenerateCurrentFrame() { HLE::SharedMemory& read = ReadRegion(); HLE::SharedMemory& write = WriteRegion(); std::array intermediate_mixes = {}; // Generate intermediate mixes for (std::size_t i = 0; i < HLE::num_sources; i++) { write.source_statuses.status[i] = sources[i].Tick(read.source_configurations.config[i], read.adpcm_coefficients.coeff[i]); for (std::size_t mix = 0; mix < 3; mix++) { sources[i].MixInto(intermediate_mixes[mix], mix); } } // Generate final mix write.dsp_status = mixers.Tick(read.dsp_configuration, read.intermediate_mix_samples, write.intermediate_mix_samples, intermediate_mixes); StereoFrame16 output_frame = mixers.GetOutput(); // Write current output frame to the shared memory region for (std::size_t samplei = 0; samplei < output_frame.size(); samplei++) { for (std::size_t channeli = 0; channeli < output_frame[0].size(); channeli++) { write.final_samples.pcm16[samplei][channeli] = s16_le(output_frame[samplei][channeli]); } } return output_frame; } bool DspHle::Impl::Tick() { StereoFrame16 current_frame = {}; // TODO: Check dsp::DSP semaphore (which indicates emulated application has finished writing to // shared memory region) current_frame = GenerateCurrentFrame(); parent.OutputFrame(std::move(current_frame)); return GetDspState() == DspState::On; } void DspHle::Impl::AudioTickCallback(s64 cycles_late) { if (Tick()) { // TODO(merry): Signal all the other interrupts as appropriate. interrupt_handler(InterruptType::Pipe, DspPipe::Audio); } // Reschedule recurrent event core_timing.ScheduleEvent(audio_frame_ticks - cycles_late, tick_event); } DspHle::DspHle(Core::System& system, Memory::MemorySystem& memory, Core::Timing& timing) : DspInterface(system), impl(std::make_unique(*this, memory, timing)) {} DspHle::~DspHle() = default; u16 DspHle::RecvData(u32 register_number) { return impl->RecvData(register_number); } bool DspHle::RecvDataIsReady(u32 register_number) const { return impl->RecvDataIsReady(register_number); } void DspHle::SetSemaphore(u16 semaphore_value) { // Do nothing in HLE } std::vector DspHle::PipeRead(DspPipe pipe_number, std::size_t length) { return impl->PipeRead(pipe_number, length); } size_t DspHle::GetPipeReadableSize(DspPipe pipe_number) const { return impl->GetPipeReadableSize(pipe_number); } void DspHle::PipeWrite(DspPipe pipe_number, std::span buffer) { impl->PipeWrite(pipe_number, buffer); } std::array& DspHle::GetDspMemory() { return impl->GetDspMemory(); } void DspHle::SetInterruptHandler( std::function handler) { impl->SetInterruptHandler(handler); }; void DspHle::LoadComponent(std::span component_data) { // HLE doesn't need DSP program. Only log some info here LOG_INFO(Service_DSP, "Firmware hash: {:#018x}", Common::ComputeHash64(component_data.data(), component_data.size())); // Some versions of the firmware have the location of DSP structures listed here. if (component_data.size() > 0x37C) { LOG_INFO(Service_DSP, "Structures hash: {:#018x}", Common::ComputeHash64(component_data.data() + 0x340, 60)); } } void DspHle::UnloadComponent() { // Do nothing } } // namespace AudioCore