audio_core: Implement audren_u audio playback.
This commit is contained in:
		| @@ -1,6 +1,8 @@ | |||||||
| add_library(audio_core STATIC | add_library(audio_core STATIC | ||||||
|     audio_out.cpp |     audio_out.cpp | ||||||
|     audio_out.h |     audio_out.h | ||||||
|  |     audio_renderer.cpp | ||||||
|  |     audio_renderer.h | ||||||
|     buffer.h |     buffer.h | ||||||
|     cubeb_sink.cpp |     cubeb_sink.cpp | ||||||
|     cubeb_sink.h |     cubeb_sink.h | ||||||
|   | |||||||
							
								
								
									
										234
									
								
								src/audio_core/audio_renderer.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										234
									
								
								src/audio_core/audio_renderer.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,234 @@ | |||||||
|  | // Copyright 2018 yuzu Emulator Project | ||||||
|  | // Licensed under GPLv2 or any later version | ||||||
|  | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
|  | #include "audio_core/audio_renderer.h" | ||||||
|  | #include "common/assert.h" | ||||||
|  | #include "common/logging/log.h" | ||||||
|  | #include "core/memory.h" | ||||||
|  |  | ||||||
|  | namespace AudioCore { | ||||||
|  |  | ||||||
|  | constexpr u32 STREAM_SAMPLE_RATE{48000}; | ||||||
|  | constexpr u32 STREAM_NUM_CHANNELS{2}; | ||||||
|  |  | ||||||
|  | AudioRenderer::AudioRenderer(AudioRendererParameter params, | ||||||
|  |                              Kernel::SharedPtr<Kernel::Event> buffer_event) | ||||||
|  |     : worker_params{params}, buffer_event{buffer_event}, voices(params.voice_count) { | ||||||
|  |  | ||||||
|  |     audio_core = std::make_unique<AudioCore::AudioOut>(); | ||||||
|  |     stream = audio_core->OpenStream(STREAM_SAMPLE_RATE, STREAM_NUM_CHANNELS, "AudioRenderer", | ||||||
|  |                                     [=]() { buffer_event->Signal(); }); | ||||||
|  |     audio_core->StartStream(stream); | ||||||
|  |  | ||||||
|  |     QueueMixedBuffer(0); | ||||||
|  |     QueueMixedBuffer(1); | ||||||
|  |     QueueMixedBuffer(2); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params) { | ||||||
|  |     // Copy UpdateDataHeader struct | ||||||
|  |     UpdateDataHeader config{}; | ||||||
|  |     std::memcpy(&config, input_params.data(), sizeof(UpdateDataHeader)); | ||||||
|  |     u32 memory_pool_count = worker_params.effect_count + (worker_params.voice_count * 4); | ||||||
|  |  | ||||||
|  |     // Copy MemoryPoolInfo structs | ||||||
|  |     std::vector<MemoryPoolInfo> mem_pool_info(memory_pool_count); | ||||||
|  |     std::memcpy(mem_pool_info.data(), | ||||||
|  |                 input_params.data() + sizeof(UpdateDataHeader) + config.behavior_size, | ||||||
|  |                 memory_pool_count * sizeof(MemoryPoolInfo)); | ||||||
|  |  | ||||||
|  |     // Copy VoiceInfo structs | ||||||
|  |     size_t offset{sizeof(UpdateDataHeader) + config.behavior_size + config.memory_pools_size + | ||||||
|  |                   config.voice_resource_size}; | ||||||
|  |     for (auto& voice : voices) { | ||||||
|  |         std::memcpy(&voice.Info(), input_params.data() + offset, sizeof(VoiceInfo)); | ||||||
|  |         offset += sizeof(VoiceInfo); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Update voices | ||||||
|  |     for (auto& voice : voices) { | ||||||
|  |         voice.UpdateState(); | ||||||
|  |         if (!voice.GetInfo().is_in_use) { | ||||||
|  |             continue; | ||||||
|  |         } | ||||||
|  |         if (voice.GetInfo().is_new) { | ||||||
|  |             voice.SetWaveIndex(voice.GetInfo().wave_buffer_head); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Update memory pool state | ||||||
|  |     std::vector<MemoryPoolEntry> memory_pool(memory_pool_count); | ||||||
|  |     for (size_t index = 0; index < memory_pool.size(); ++index) { | ||||||
|  |         if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestAttach) { | ||||||
|  |             memory_pool[index].state = MemoryPoolStates::Attached; | ||||||
|  |         } else if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestDetach) { | ||||||
|  |             memory_pool[index].state = MemoryPoolStates::Detached; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Release previous buffers and queue next ones for playback | ||||||
|  |     ReleaseAndQueueBuffers(); | ||||||
|  |  | ||||||
|  |     // Copy output header | ||||||
|  |     UpdateDataHeader response_data{worker_params}; | ||||||
|  |     std::vector<u8> output_params(response_data.total_size); | ||||||
|  |     std::memcpy(output_params.data(), &response_data, sizeof(UpdateDataHeader)); | ||||||
|  |  | ||||||
|  |     // Copy output memory pool entries | ||||||
|  |     std::memcpy(output_params.data() + sizeof(UpdateDataHeader), memory_pool.data(), | ||||||
|  |                 response_data.memory_pools_size); | ||||||
|  |  | ||||||
|  |     // Copy output voice status | ||||||
|  |     size_t voice_out_status_offset{sizeof(UpdateDataHeader) + response_data.memory_pools_size}; | ||||||
|  |     for (const auto& voice : voices) { | ||||||
|  |         std::memcpy(output_params.data() + voice_out_status_offset, &voice.GetOutStatus(), | ||||||
|  |                     sizeof(VoiceOutStatus)); | ||||||
|  |         voice_out_status_offset += sizeof(VoiceOutStatus); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return output_params; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void AudioRenderer::VoiceState::SetWaveIndex(size_t index) { | ||||||
|  |     wave_index = index & 3; | ||||||
|  |     is_refresh_pending = true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | std::vector<s16> AudioRenderer::VoiceState::DequeueSamples(size_t sample_count) { | ||||||
|  |     if (!IsPlaying()) { | ||||||
|  |         return {}; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (is_refresh_pending) { | ||||||
|  |         RefreshBuffer(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const size_t max_size{samples.size() - offset}; | ||||||
|  |     const size_t dequeue_offset{offset}; | ||||||
|  |     size_t size{sample_count * STREAM_NUM_CHANNELS}; | ||||||
|  |     if (size > max_size) { | ||||||
|  |         size = max_size; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     out_status.played_sample_count += size / STREAM_NUM_CHANNELS; | ||||||
|  |     offset += size; | ||||||
|  |  | ||||||
|  |     const auto& wave_buffer{info.wave_buffer[wave_index]}; | ||||||
|  |     if (offset == samples.size()) { | ||||||
|  |         offset = 0; | ||||||
|  |  | ||||||
|  |         if (!wave_buffer.is_looping) { | ||||||
|  |             SetWaveIndex(wave_index + 1); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         out_status.wave_buffer_consumed++; | ||||||
|  |  | ||||||
|  |         if (wave_buffer.end_of_stream) { | ||||||
|  |             info.play_state = PlayState::Paused; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return {samples.begin() + dequeue_offset, samples.begin() + dequeue_offset + size}; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void AudioRenderer::VoiceState::UpdateState() { | ||||||
|  |     if (is_in_use && !info.is_in_use) { | ||||||
|  |         // No longer in use, reset state | ||||||
|  |         is_refresh_pending = true; | ||||||
|  |         wave_index = 0; | ||||||
|  |         offset = 0; | ||||||
|  |         out_status = {}; | ||||||
|  |     } | ||||||
|  |     is_in_use = info.is_in_use; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void AudioRenderer::VoiceState::RefreshBuffer() { | ||||||
|  |     std::vector<s16> new_samples(info.wave_buffer[wave_index].buffer_sz / sizeof(s16)); | ||||||
|  |     Memory::ReadBlock(info.wave_buffer[wave_index].buffer_addr, new_samples.data(), | ||||||
|  |                       info.wave_buffer[wave_index].buffer_sz); | ||||||
|  |  | ||||||
|  |     switch (static_cast<Codec::PcmFormat>(info.sample_format)) { | ||||||
|  |     case Codec::PcmFormat::Int16: { | ||||||
|  |         // PCM16 is played as-is | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |     case Codec::PcmFormat::Adpcm: { | ||||||
|  |         // Decode ADPCM to PCM16 | ||||||
|  |         Codec::ADPCM_Coeff coeffs; | ||||||
|  |         Memory::ReadBlock(info.additional_params_addr, coeffs.data(), sizeof(Codec::ADPCM_Coeff)); | ||||||
|  |         new_samples = Codec::DecodeADPCM(reinterpret_cast<u8*>(new_samples.data()), | ||||||
|  |                                          new_samples.size() * sizeof(s16), coeffs, adpcm_state); | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |     default: | ||||||
|  |         LOG_CRITICAL(Audio, "Unimplemented sample_format={}", info.sample_format); | ||||||
|  |         UNREACHABLE(); | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     switch (info.channel_count) { | ||||||
|  |     case 1: | ||||||
|  |         // 1 channel is upsampled to 2 channel | ||||||
|  |         samples.resize(new_samples.size() * 2); | ||||||
|  |         for (size_t index = 0; index < new_samples.size(); ++index) { | ||||||
|  |             samples[index * 2] = new_samples[index]; | ||||||
|  |             samples[index * 2 + 1] = new_samples[index]; | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  |     case 2: { | ||||||
|  |         // 2 channel is played as is | ||||||
|  |         samples = std::move(new_samples); | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |     default: | ||||||
|  |         LOG_CRITICAL(Audio, "Unimplemented channel_count={}", info.channel_count); | ||||||
|  |         UNREACHABLE(); | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     is_refresh_pending = false; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static constexpr s16 ClampToS16(s32 value) { | ||||||
|  |     return static_cast<s16>(std::clamp(value, -32768, 32767)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) { | ||||||
|  |     constexpr size_t BUFFER_SIZE{512}; | ||||||
|  |     std::vector<s16> buffer(BUFFER_SIZE * stream->GetNumChannels()); | ||||||
|  |  | ||||||
|  |     for (auto& voice : voices) { | ||||||
|  |         if (!voice.IsPlaying()) { | ||||||
|  |             continue; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         size_t offset{}; | ||||||
|  |         s64 samples_remaining{BUFFER_SIZE}; | ||||||
|  |         while (samples_remaining > 0) { | ||||||
|  |             const std::vector<s16> samples{voice.DequeueSamples(samples_remaining)}; | ||||||
|  |  | ||||||
|  |             if (samples.empty()) { | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             samples_remaining -= samples.size(); | ||||||
|  |  | ||||||
|  |             for (const auto& sample : samples) { | ||||||
|  |                 const s32 buffer_sample{buffer[offset]}; | ||||||
|  |                 buffer[offset++] = | ||||||
|  |                     ClampToS16(buffer_sample + static_cast<s32>(sample * voice.GetInfo().volume)); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     audio_core->QueueBuffer(stream, tag, std::move(buffer)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void AudioRenderer::ReleaseAndQueueBuffers() { | ||||||
|  |     const auto released_buffers{audio_core->GetTagsAndReleaseBuffers(stream, 2)}; | ||||||
|  |     for (const auto& tag : released_buffers) { | ||||||
|  |         QueueMixedBuffer(tag); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } // namespace AudioCore | ||||||
							
								
								
									
										206
									
								
								src/audio_core/audio_renderer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										206
									
								
								src/audio_core/audio_renderer.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,206 @@ | |||||||
|  | // Copyright 2018 yuzu Emulator Project | ||||||
|  | // Licensed under GPLv2 or any later version | ||||||
|  | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include <array> | ||||||
|  | #include <memory> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | #include "audio_core/audio_out.h" | ||||||
|  | #include "audio_core/codec.h" | ||||||
|  | #include "audio_core/stream.h" | ||||||
|  | #include "common/common_types.h" | ||||||
|  | #include "common/swap.h" | ||||||
|  | #include "core/hle/kernel/event.h" | ||||||
|  |  | ||||||
|  | namespace AudioCore { | ||||||
|  |  | ||||||
|  | enum class PlayState : u8 { | ||||||
|  |     Started = 0, | ||||||
|  |     Stopped = 1, | ||||||
|  |     Paused = 2, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | struct AudioRendererParameter { | ||||||
|  |     u32_le sample_rate; | ||||||
|  |     u32_le sample_count; | ||||||
|  |     u32_le unknown_8; | ||||||
|  |     u32_le unknown_c; | ||||||
|  |     u32_le voice_count; | ||||||
|  |     u32_le sink_count; | ||||||
|  |     u32_le effect_count; | ||||||
|  |     u32_le unknown_1c; | ||||||
|  |     u8 unknown_20; | ||||||
|  |     INSERT_PADDING_BYTES(3); | ||||||
|  |     u32_le splitter_count; | ||||||
|  |     u32_le unknown_2c; | ||||||
|  |     INSERT_PADDING_WORDS(1); | ||||||
|  |     u32_le revision; | ||||||
|  | }; | ||||||
|  | static_assert(sizeof(AudioRendererParameter) == 52, "AudioRendererParameter is an invalid size"); | ||||||
|  |  | ||||||
|  | enum class MemoryPoolStates : u32 { // Should be LE | ||||||
|  |     Invalid = 0x0, | ||||||
|  |     Unknown = 0x1, | ||||||
|  |     RequestDetach = 0x2, | ||||||
|  |     Detached = 0x3, | ||||||
|  |     RequestAttach = 0x4, | ||||||
|  |     Attached = 0x5, | ||||||
|  |     Released = 0x6, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | struct MemoryPoolEntry { | ||||||
|  |     MemoryPoolStates state; | ||||||
|  |     u32_le unknown_4; | ||||||
|  |     u32_le unknown_8; | ||||||
|  |     u32_le unknown_c; | ||||||
|  | }; | ||||||
|  | static_assert(sizeof(MemoryPoolEntry) == 0x10, "MemoryPoolEntry has wrong size"); | ||||||
|  |  | ||||||
|  | struct MemoryPoolInfo { | ||||||
|  |     u64_le pool_address; | ||||||
|  |     u64_le pool_size; | ||||||
|  |     MemoryPoolStates pool_state; | ||||||
|  |     INSERT_PADDING_WORDS(3); // Unknown | ||||||
|  | }; | ||||||
|  | static_assert(sizeof(MemoryPoolInfo) == 0x20, "MemoryPoolInfo has wrong size"); | ||||||
|  | struct BiquadFilter { | ||||||
|  |     u8 enable; | ||||||
|  |     INSERT_PADDING_BYTES(1); | ||||||
|  |     std::array<s16_le, 3> numerator; | ||||||
|  |     std::array<s16_le, 2> denominator; | ||||||
|  | }; | ||||||
|  | static_assert(sizeof(BiquadFilter) == 0xc, "BiquadFilter has wrong size"); | ||||||
|  |  | ||||||
|  | struct WaveBuffer { | ||||||
|  |     u64_le buffer_addr; | ||||||
|  |     u64_le buffer_sz; | ||||||
|  |     s32_le start_sample_offset; | ||||||
|  |     s32_le end_sample_offset; | ||||||
|  |     u8 is_looping; | ||||||
|  |     u8 end_of_stream; | ||||||
|  |     u8 sent_to_server; | ||||||
|  |     INSERT_PADDING_BYTES(5); | ||||||
|  |     u64 context_addr; | ||||||
|  |     u64 context_sz; | ||||||
|  |     INSERT_PADDING_BYTES(8); | ||||||
|  | }; | ||||||
|  | static_assert(sizeof(WaveBuffer) == 0x38, "WaveBuffer has wrong size"); | ||||||
|  |  | ||||||
|  | struct VoiceInfo { | ||||||
|  |     u32_le id; | ||||||
|  |     u32_le node_id; | ||||||
|  |     u8 is_new; | ||||||
|  |     u8 is_in_use; | ||||||
|  |     PlayState play_state; | ||||||
|  |     u8 sample_format; | ||||||
|  |     u32_le sample_rate; | ||||||
|  |     u32_le priority; | ||||||
|  |     u32_le sorting_order; | ||||||
|  |     u32_le channel_count; | ||||||
|  |     float_le pitch; | ||||||
|  |     float_le volume; | ||||||
|  |     std::array<BiquadFilter, 2> biquad_filter; | ||||||
|  |     u32_le wave_buffer_count; | ||||||
|  |     u32_le wave_buffer_head; | ||||||
|  |     INSERT_PADDING_WORDS(1); | ||||||
|  |     u64_le additional_params_addr; | ||||||
|  |     u64_le additional_params_sz; | ||||||
|  |     u32_le mix_id; | ||||||
|  |     u32_le splitter_info_id; | ||||||
|  |     std::array<WaveBuffer, 4> wave_buffer; | ||||||
|  |     std::array<u32_le, 6> voice_channel_resource_ids; | ||||||
|  |     INSERT_PADDING_BYTES(24); | ||||||
|  | }; | ||||||
|  | static_assert(sizeof(VoiceInfo) == 0x170, "VoiceInfo is wrong size"); | ||||||
|  |  | ||||||
|  | struct VoiceOutStatus { | ||||||
|  |     u64_le played_sample_count; | ||||||
|  |     u32_le wave_buffer_consumed; | ||||||
|  |     u32_le voice_drops_count; | ||||||
|  | }; | ||||||
|  | static_assert(sizeof(VoiceOutStatus) == 0x10, "VoiceOutStatus has wrong size"); | ||||||
|  |  | ||||||
|  | struct UpdateDataHeader { | ||||||
|  |     UpdateDataHeader() {} | ||||||
|  |  | ||||||
|  |     explicit UpdateDataHeader(const AudioRendererParameter& config) { | ||||||
|  |         revision = Common::MakeMagic('R', 'E', 'V', '4'); // 5.1.0 Revision | ||||||
|  |         behavior_size = 0xb0; | ||||||
|  |         memory_pools_size = (config.effect_count + (config.voice_count * 4)) * 0x10; | ||||||
|  |         voices_size = config.voice_count * 0x10; | ||||||
|  |         voice_resource_size = 0x0; | ||||||
|  |         effects_size = config.effect_count * 0x10; | ||||||
|  |         mixes_size = 0x0; | ||||||
|  |         sinks_size = config.sink_count * 0x20; | ||||||
|  |         performance_manager_size = 0x10; | ||||||
|  |         total_size = sizeof(UpdateDataHeader) + behavior_size + memory_pools_size + voices_size + | ||||||
|  |                      effects_size + sinks_size + performance_manager_size; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     u32_le revision; | ||||||
|  |     u32_le behavior_size; | ||||||
|  |     u32_le memory_pools_size; | ||||||
|  |     u32_le voices_size; | ||||||
|  |     u32_le voice_resource_size; | ||||||
|  |     u32_le effects_size; | ||||||
|  |     u32_le mixes_size; | ||||||
|  |     u32_le sinks_size; | ||||||
|  |     u32_le performance_manager_size; | ||||||
|  |     INSERT_PADDING_WORDS(6); | ||||||
|  |     u32_le total_size; | ||||||
|  | }; | ||||||
|  | static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader has wrong size"); | ||||||
|  |  | ||||||
|  | class AudioRenderer { | ||||||
|  | public: | ||||||
|  |     AudioRenderer(AudioRendererParameter params, Kernel::SharedPtr<Kernel::Event> buffer_event); | ||||||
|  |     std::vector<u8> UpdateAudioRenderer(const std::vector<u8>& input_params); | ||||||
|  |     void QueueMixedBuffer(Buffer::Tag tag); | ||||||
|  |     void ReleaseAndQueueBuffers(); | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |     class VoiceState { | ||||||
|  |     public: | ||||||
|  |         bool IsPlaying() const { | ||||||
|  |             return is_in_use && info.play_state == PlayState::Started; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         const VoiceOutStatus& GetOutStatus() const { | ||||||
|  |             return out_status; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         const VoiceInfo& GetInfo() const { | ||||||
|  |             return info; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         VoiceInfo& Info() { | ||||||
|  |             return info; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         void SetWaveIndex(size_t index); | ||||||
|  |         std::vector<s16> DequeueSamples(size_t sample_count); | ||||||
|  |         void UpdateState(); | ||||||
|  |         void RefreshBuffer(); | ||||||
|  |  | ||||||
|  |     private: | ||||||
|  |         bool is_in_use{}; | ||||||
|  |         bool is_refresh_pending{}; | ||||||
|  |         size_t wave_index{}; | ||||||
|  |         size_t offset{}; | ||||||
|  |         Codec::ADPCMState adpcm_state{}; | ||||||
|  |         std::vector<s16> samples; | ||||||
|  |         VoiceOutStatus out_status{}; | ||||||
|  |         VoiceInfo info{}; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     AudioRendererParameter worker_params; | ||||||
|  |     Kernel::SharedPtr<Kernel::Event> buffer_event; | ||||||
|  |     std::vector<VoiceState> voices; | ||||||
|  |     std::unique_ptr<AudioCore::AudioOut> audio_core; | ||||||
|  |     AudioCore::StreamPtr stream; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } // namespace AudioCore | ||||||
| @@ -15,13 +15,10 @@ | |||||||
|  |  | ||||||
| namespace Service::Audio { | namespace Service::Audio { | ||||||
|  |  | ||||||
| /// TODO(bunnei): Find a proper value for the audio_ticks |  | ||||||
| constexpr u64 audio_ticks{static_cast<u64>(CoreTiming::BASE_CLOCK_RATE / 200)}; |  | ||||||
|  |  | ||||||
| class IAudioRenderer final : public ServiceFramework<IAudioRenderer> { | class IAudioRenderer final : public ServiceFramework<IAudioRenderer> { | ||||||
| public: | public: | ||||||
|     explicit IAudioRenderer(AudioRendererParameter audren_params) |     explicit IAudioRenderer(AudioCore::AudioRendererParameter audren_params) | ||||||
|         : ServiceFramework("IAudioRenderer"), worker_params(audren_params) { |         : ServiceFramework("IAudioRenderer") { | ||||||
|         static const FunctionInfo functions[] = { |         static const FunctionInfo functions[] = { | ||||||
|             {0, nullptr, "GetAudioRendererSampleRate"}, |             {0, nullptr, "GetAudioRendererSampleRate"}, | ||||||
|             {1, nullptr, "GetAudioRendererSampleCount"}, |             {1, nullptr, "GetAudioRendererSampleCount"}, | ||||||
| @@ -39,21 +36,8 @@ public: | |||||||
|         RegisterHandlers(functions); |         RegisterHandlers(functions); | ||||||
|  |  | ||||||
|         system_event = |         system_event = | ||||||
|             Kernel::Event::Create(Kernel::ResetType::OneShot, "IAudioRenderer:SystemEvent"); |             Kernel::Event::Create(Kernel::ResetType::Sticky, "IAudioRenderer:SystemEvent"); | ||||||
|  |         renderer = std::make_unique<AudioCore::AudioRenderer>(audren_params, system_event); | ||||||
|         // Register event callback to update the Audio Buffer |  | ||||||
|         audio_event = CoreTiming::RegisterEvent( |  | ||||||
|             "IAudioRenderer::UpdateAudioCallback", [this](u64 userdata, int cycles_late) { |  | ||||||
|                 UpdateAudioCallback(); |  | ||||||
|                 CoreTiming::ScheduleEvent(audio_ticks - cycles_late, audio_event); |  | ||||||
|             }); |  | ||||||
|  |  | ||||||
|         // Start the audio event |  | ||||||
|         CoreTiming::ScheduleEvent(audio_ticks, audio_event); |  | ||||||
|         voice_status_list.resize(worker_params.voice_count); |  | ||||||
|     } |  | ||||||
|     ~IAudioRenderer() { |  | ||||||
|         CoreTiming::UnscheduleEvent(audio_event, 0); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
| private: | private: | ||||||
| @@ -62,60 +46,9 @@ private: | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     void RequestUpdateAudioRenderer(Kernel::HLERequestContext& ctx) { |     void RequestUpdateAudioRenderer(Kernel::HLERequestContext& ctx) { | ||||||
|         UpdateDataHeader config{}; |         ctx.WriteBuffer(renderer->UpdateAudioRenderer(ctx.ReadBuffer())); | ||||||
|         auto buf = ctx.ReadBuffer(); |  | ||||||
|         std::memcpy(&config, buf.data(), sizeof(UpdateDataHeader)); |  | ||||||
|         u32 memory_pool_count = worker_params.effect_count + (worker_params.voice_count * 4); |  | ||||||
|  |  | ||||||
|         std::vector<MemoryPoolInfo> mem_pool_info(memory_pool_count); |  | ||||||
|         std::memcpy(mem_pool_info.data(), |  | ||||||
|                     buf.data() + sizeof(UpdateDataHeader) + config.behavior_size, |  | ||||||
|                     memory_pool_count * sizeof(MemoryPoolInfo)); |  | ||||||
|  |  | ||||||
|         std::vector<VoiceInfo> voice_info(worker_params.voice_count); |  | ||||||
|         std::memcpy(voice_info.data(), |  | ||||||
|                     buf.data() + sizeof(UpdateDataHeader) + config.behavior_size + |  | ||||||
|                         config.memory_pools_size + config.voice_resource_size, |  | ||||||
|                     worker_params.voice_count * sizeof(VoiceInfo)); |  | ||||||
|  |  | ||||||
|         UpdateDataHeader response_data{worker_params}; |  | ||||||
|  |  | ||||||
|         ASSERT(ctx.GetWriteBufferSize() == response_data.total_size); |  | ||||||
|  |  | ||||||
|         std::vector<u8> output(response_data.total_size); |  | ||||||
|         std::memcpy(output.data(), &response_data, sizeof(UpdateDataHeader)); |  | ||||||
|         std::vector<MemoryPoolEntry> memory_pool(memory_pool_count); |  | ||||||
|         for (unsigned i = 0; i < memory_pool.size(); i++) { |  | ||||||
|             if (mem_pool_info[i].pool_state == MemoryPoolStates::RequestAttach) |  | ||||||
|                 memory_pool[i].state = MemoryPoolStates::Attached; |  | ||||||
|             else if (mem_pool_info[i].pool_state == MemoryPoolStates::RequestDetach) |  | ||||||
|                 memory_pool[i].state = MemoryPoolStates::Detached; |  | ||||||
|         } |  | ||||||
|         std::memcpy(output.data() + sizeof(UpdateDataHeader), memory_pool.data(), |  | ||||||
|                     response_data.memory_pools_size); |  | ||||||
|  |  | ||||||
|         for (unsigned i = 0; i < voice_info.size(); i++) { |  | ||||||
|             if (voice_info[i].is_new) { |  | ||||||
|                 voice_status_list[i].played_sample_count = 0; |  | ||||||
|                 voice_status_list[i].wave_buffer_consumed = 0; |  | ||||||
|             } else if (voice_info[i].play_state == (u8)PlayStates::Started) { |  | ||||||
|                 for (u32 buff_idx = 0; buff_idx < voice_info[i].wave_buffer_count; buff_idx++) { |  | ||||||
|                     voice_status_list[i].played_sample_count += |  | ||||||
|                         (voice_info[i].wave_buffer[buff_idx].end_sample_offset - |  | ||||||
|                          voice_info[i].wave_buffer[buff_idx].start_sample_offset) / |  | ||||||
|                         2; |  | ||||||
|                     voice_status_list[i].wave_buffer_consumed++; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         std::memcpy(output.data() + sizeof(UpdateDataHeader) + response_data.memory_pools_size, |  | ||||||
|                     voice_status_list.data(), response_data.voices_size); |  | ||||||
|  |  | ||||||
|         ctx.WriteBuffer(output); |  | ||||||
|  |  | ||||||
|         IPC::ResponseBuilder rb{ctx, 2}; |         IPC::ResponseBuilder rb{ctx, 2}; | ||||||
|         rb.Push(RESULT_SUCCESS); |         rb.Push(RESULT_SUCCESS); | ||||||
|  |  | ||||||
|         LOG_WARNING(Service_Audio, "(STUBBED) called"); |         LOG_WARNING(Service_Audio, "(STUBBED) called"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -136,8 +69,6 @@ private: | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     void QuerySystemEvent(Kernel::HLERequestContext& ctx) { |     void QuerySystemEvent(Kernel::HLERequestContext& ctx) { | ||||||
|         // system_event->Signal(); |  | ||||||
|  |  | ||||||
|         IPC::ResponseBuilder rb{ctx, 2, 1}; |         IPC::ResponseBuilder rb{ctx, 2, 1}; | ||||||
|         rb.Push(RESULT_SUCCESS); |         rb.Push(RESULT_SUCCESS); | ||||||
|         rb.PushCopyObjects(system_event); |         rb.PushCopyObjects(system_event); | ||||||
| @@ -145,131 +76,8 @@ private: | |||||||
|         LOG_WARNING(Service_Audio, "(STUBBED) called"); |         LOG_WARNING(Service_Audio, "(STUBBED) called"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     enum class MemoryPoolStates : u32 { // Should be LE |  | ||||||
|         Invalid = 0x0, |  | ||||||
|         Unknown = 0x1, |  | ||||||
|         RequestDetach = 0x2, |  | ||||||
|         Detached = 0x3, |  | ||||||
|         RequestAttach = 0x4, |  | ||||||
|         Attached = 0x5, |  | ||||||
|         Released = 0x6, |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     enum class PlayStates : u8 { |  | ||||||
|         Started = 0, |  | ||||||
|         Stopped = 1, |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     struct MemoryPoolEntry { |  | ||||||
|         MemoryPoolStates state; |  | ||||||
|         u32_le unknown_4; |  | ||||||
|         u32_le unknown_8; |  | ||||||
|         u32_le unknown_c; |  | ||||||
|     }; |  | ||||||
|     static_assert(sizeof(MemoryPoolEntry) == 0x10, "MemoryPoolEntry has wrong size"); |  | ||||||
|  |  | ||||||
|     struct MemoryPoolInfo { |  | ||||||
|         u64_le pool_address; |  | ||||||
|         u64_le pool_size; |  | ||||||
|         MemoryPoolStates pool_state; |  | ||||||
|         INSERT_PADDING_WORDS(3); // Unknown |  | ||||||
|     }; |  | ||||||
|     static_assert(sizeof(MemoryPoolInfo) == 0x20, "MemoryPoolInfo has wrong size"); |  | ||||||
|  |  | ||||||
|     struct UpdateDataHeader { |  | ||||||
|         UpdateDataHeader() {} |  | ||||||
|  |  | ||||||
|         explicit UpdateDataHeader(const AudioRendererParameter& config) { |  | ||||||
|             revision = Common::MakeMagic('R', 'E', 'V', '4'); // 5.1.0 Revision |  | ||||||
|             behavior_size = 0xb0; |  | ||||||
|             memory_pools_size = (config.effect_count + (config.voice_count * 4)) * 0x10; |  | ||||||
|             voices_size = config.voice_count * 0x10; |  | ||||||
|             voice_resource_size = 0x0; |  | ||||||
|             effects_size = config.effect_count * 0x10; |  | ||||||
|             mixes_size = 0x0; |  | ||||||
|             sinks_size = config.sink_count * 0x20; |  | ||||||
|             performance_manager_size = 0x10; |  | ||||||
|             total_size = sizeof(UpdateDataHeader) + behavior_size + memory_pools_size + |  | ||||||
|                          voices_size + effects_size + sinks_size + performance_manager_size; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         u32_le revision; |  | ||||||
|         u32_le behavior_size; |  | ||||||
|         u32_le memory_pools_size; |  | ||||||
|         u32_le voices_size; |  | ||||||
|         u32_le voice_resource_size; |  | ||||||
|         u32_le effects_size; |  | ||||||
|         u32_le mixes_size; |  | ||||||
|         u32_le sinks_size; |  | ||||||
|         u32_le performance_manager_size; |  | ||||||
|         INSERT_PADDING_WORDS(6); |  | ||||||
|         u32_le total_size; |  | ||||||
|     }; |  | ||||||
|     static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader has wrong size"); |  | ||||||
|  |  | ||||||
|     struct BiquadFilter { |  | ||||||
|         u8 enable; |  | ||||||
|         INSERT_PADDING_BYTES(1); |  | ||||||
|         s16_le numerator[3]; |  | ||||||
|         s16_le denominator[2]; |  | ||||||
|     }; |  | ||||||
|     static_assert(sizeof(BiquadFilter) == 0xc, "BiquadFilter has wrong size"); |  | ||||||
|  |  | ||||||
|     struct WaveBuffer { |  | ||||||
|         u64_le buffer_addr; |  | ||||||
|         u64_le buffer_sz; |  | ||||||
|         s32_le start_sample_offset; |  | ||||||
|         s32_le end_sample_offset; |  | ||||||
|         u8 loop; |  | ||||||
|         u8 end_of_stream; |  | ||||||
|         u8 sent_to_server; |  | ||||||
|         INSERT_PADDING_BYTES(5); |  | ||||||
|         u64 context_addr; |  | ||||||
|         u64 context_sz; |  | ||||||
|         INSERT_PADDING_BYTES(8); |  | ||||||
|     }; |  | ||||||
|     static_assert(sizeof(WaveBuffer) == 0x38, "WaveBuffer has wrong size"); |  | ||||||
|  |  | ||||||
|     struct VoiceInfo { |  | ||||||
|         u32_le id; |  | ||||||
|         u32_le node_id; |  | ||||||
|         u8 is_new; |  | ||||||
|         u8 is_in_use; |  | ||||||
|         u8 play_state; |  | ||||||
|         u8 sample_format; |  | ||||||
|         u32_le sample_rate; |  | ||||||
|         u32_le priority; |  | ||||||
|         u32_le sorting_order; |  | ||||||
|         u32_le channel_count; |  | ||||||
|         float_le pitch; |  | ||||||
|         float_le volume; |  | ||||||
|         BiquadFilter biquad_filter[2]; |  | ||||||
|         u32_le wave_buffer_count; |  | ||||||
|         u16_le wave_buffer_head; |  | ||||||
|         INSERT_PADDING_BYTES(6); |  | ||||||
|         u64_le additional_params_addr; |  | ||||||
|         u64_le additional_params_sz; |  | ||||||
|         u32_le mix_id; |  | ||||||
|         u32_le splitter_info_id; |  | ||||||
|         WaveBuffer wave_buffer[4]; |  | ||||||
|         u32_le voice_channel_resource_ids[6]; |  | ||||||
|         INSERT_PADDING_BYTES(24); |  | ||||||
|     }; |  | ||||||
|     static_assert(sizeof(VoiceInfo) == 0x170, "VoiceInfo is wrong size"); |  | ||||||
|  |  | ||||||
|     struct VoiceOutStatus { |  | ||||||
|         u64_le played_sample_count; |  | ||||||
|         u32_le wave_buffer_consumed; |  | ||||||
|         INSERT_PADDING_WORDS(1); |  | ||||||
|     }; |  | ||||||
|     static_assert(sizeof(VoiceOutStatus) == 0x10, "VoiceOutStatus has wrong size"); |  | ||||||
|  |  | ||||||
|     /// This is used to trigger the audio event callback. |  | ||||||
|     CoreTiming::EventType* audio_event; |  | ||||||
|  |  | ||||||
|     Kernel::SharedPtr<Kernel::Event> system_event; |     Kernel::SharedPtr<Kernel::Event> system_event; | ||||||
|     AudioRendererParameter worker_params; |     std::unique_ptr<AudioCore::AudioRenderer> renderer; | ||||||
|     std::vector<VoiceOutStatus> voice_status_list; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class IAudioDevice final : public ServiceFramework<IAudioDevice> { | class IAudioDevice final : public ServiceFramework<IAudioDevice> { | ||||||
| @@ -368,7 +176,7 @@ AudRenU::AudRenU() : ServiceFramework("audren:u") { | |||||||
|  |  | ||||||
| void AudRenU::OpenAudioRenderer(Kernel::HLERequestContext& ctx) { | void AudRenU::OpenAudioRenderer(Kernel::HLERequestContext& ctx) { | ||||||
|     IPC::RequestParser rp{ctx}; |     IPC::RequestParser rp{ctx}; | ||||||
|     auto params = rp.PopRaw<AudioRendererParameter>(); |     auto params = rp.PopRaw<AudioCore::AudioRendererParameter>(); | ||||||
|     IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |     IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | ||||||
|  |  | ||||||
|     rb.Push(RESULT_SUCCESS); |     rb.Push(RESULT_SUCCESS); | ||||||
| @@ -379,7 +187,7 @@ void AudRenU::OpenAudioRenderer(Kernel::HLERequestContext& ctx) { | |||||||
|  |  | ||||||
| void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) { | void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) { | ||||||
|     IPC::RequestParser rp{ctx}; |     IPC::RequestParser rp{ctx}; | ||||||
|     auto params = rp.PopRaw<AudioRendererParameter>(); |     auto params = rp.PopRaw<AudioCore::AudioRendererParameter>(); | ||||||
|  |  | ||||||
|     u64 buffer_sz = Common::AlignUp(4 * params.unknown_8, 0x40); |     u64 buffer_sz = Common::AlignUp(4 * params.unknown_8, 0x40); | ||||||
|     buffer_sz += params.unknown_c * 1024; |     buffer_sz += params.unknown_c * 1024; | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ | |||||||
|  |  | ||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
|  | #include "audio_core/audio_renderer.h" | ||||||
| #include "core/hle/service/service.h" | #include "core/hle/service/service.h" | ||||||
|  |  | ||||||
| namespace Kernel { | namespace Kernel { | ||||||
| @@ -12,24 +13,6 @@ class HLERequestContext; | |||||||
|  |  | ||||||
| namespace Service::Audio { | namespace Service::Audio { | ||||||
|  |  | ||||||
| struct AudioRendererParameter { |  | ||||||
|     u32_le sample_rate; |  | ||||||
|     u32_le sample_count; |  | ||||||
|     u32_le unknown_8; |  | ||||||
|     u32_le unknown_c; |  | ||||||
|     u32_le voice_count; |  | ||||||
|     u32_le sink_count; |  | ||||||
|     u32_le effect_count; |  | ||||||
|     u32_le unknown_1c; |  | ||||||
|     u8 unknown_20; |  | ||||||
|     INSERT_PADDING_BYTES(3); |  | ||||||
|     u32_le splitter_count; |  | ||||||
|     u32_le unknown_2c; |  | ||||||
|     INSERT_PADDING_WORDS(1); |  | ||||||
|     u32_le revision; |  | ||||||
| }; |  | ||||||
| static_assert(sizeof(AudioRendererParameter) == 52, "AudioRendererParameter is an invalid size"); |  | ||||||
|  |  | ||||||
| class AudRenU final : public ServiceFramework<AudRenU> { | class AudRenU final : public ServiceFramework<AudRenU> { | ||||||
| public: | public: | ||||||
|     explicit AudRenU(); |     explicit AudRenU(); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user