Merge pull request #925 from bunnei/audren
Implement audren audio output
This commit is contained in:
		| @@ -1,9 +1,13 @@ | ||||
| add_library(audio_core STATIC | ||||
|     audio_out.cpp | ||||
|     audio_out.h | ||||
|     audio_renderer.cpp | ||||
|     audio_renderer.h | ||||
|     buffer.h | ||||
|     cubeb_sink.cpp | ||||
|     cubeb_sink.h | ||||
|     codec.cpp | ||||
|     codec.h | ||||
|     null_sink.h | ||||
|     stream.cpp | ||||
|     stream.h | ||||
|   | ||||
| @@ -27,16 +27,16 @@ static Stream::Format ChannelsToStreamFormat(u32 num_channels) { | ||||
|     return {}; | ||||
| } | ||||
|  | ||||
| StreamPtr AudioOut::OpenStream(u32 sample_rate, u32 num_channels, | ||||
| StreamPtr AudioOut::OpenStream(u32 sample_rate, u32 num_channels, std::string&& name, | ||||
|                                Stream::ReleaseCallback&& release_callback) { | ||||
|     if (!sink) { | ||||
|         const SinkDetails& sink_details = GetSinkDetails(Settings::values.sink_id); | ||||
|         sink = sink_details.factory(Settings::values.audio_device_id); | ||||
|     } | ||||
|  | ||||
|     return std::make_shared<Stream>(sample_rate, ChannelsToStreamFormat(num_channels), | ||||
|                                     std::move(release_callback), | ||||
|                                     sink->AcquireSinkStream(sample_rate, num_channels)); | ||||
|     return std::make_shared<Stream>( | ||||
|         sample_rate, ChannelsToStreamFormat(num_channels), std::move(release_callback), | ||||
|         sink->AcquireSinkStream(sample_rate, num_channels, name), std::move(name)); | ||||
| } | ||||
|  | ||||
| std::vector<Buffer::Tag> AudioOut::GetTagsAndReleaseBuffers(StreamPtr stream, size_t max_count) { | ||||
| @@ -51,7 +51,7 @@ void AudioOut::StopStream(StreamPtr stream) { | ||||
|     stream->Stop(); | ||||
| } | ||||
|  | ||||
| bool AudioOut::QueueBuffer(StreamPtr stream, Buffer::Tag tag, std::vector<u8>&& data) { | ||||
| bool AudioOut::QueueBuffer(StreamPtr stream, Buffer::Tag tag, std::vector<s16>&& data) { | ||||
|     return stream->QueueBuffer(std::make_shared<Buffer>(tag, std::move(data))); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -5,6 +5,7 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include <vector> | ||||
|  | ||||
| #include "audio_core/buffer.h" | ||||
| @@ -20,7 +21,7 @@ namespace AudioCore { | ||||
| class AudioOut { | ||||
| public: | ||||
|     /// Opens a new audio stream | ||||
|     StreamPtr OpenStream(u32 sample_rate, u32 num_channels, | ||||
|     StreamPtr OpenStream(u32 sample_rate, u32 num_channels, std::string&& name, | ||||
|                          Stream::ReleaseCallback&& release_callback); | ||||
|  | ||||
|     /// Returns a vector of recently released buffers specified by tag for the specified stream | ||||
| @@ -33,7 +34,7 @@ public: | ||||
|     void StopStream(StreamPtr stream); | ||||
|  | ||||
|     /// Queues a buffer into the specified audio stream, returns true on success | ||||
|     bool QueueBuffer(StreamPtr stream, Buffer::Tag tag, std::vector<u8>&& data); | ||||
|     bool QueueBuffer(StreamPtr stream, Buffer::Tag tag, std::vector<s16>&& data); | ||||
|  | ||||
| private: | ||||
|     SinkPtr sink; | ||||
|   | ||||
							
								
								
									
										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 | ||||
| @@ -18,11 +18,16 @@ class Buffer { | ||||
| public: | ||||
|     using Tag = u64; | ||||
|  | ||||
|     Buffer(Tag tag, std::vector<u8>&& data) : tag{tag}, data{std::move(data)} {} | ||||
|     Buffer(Tag tag, std::vector<s16>&& samples) : tag{tag}, samples{std::move(samples)} {} | ||||
|  | ||||
|     /// Returns the raw audio data for the buffer | ||||
|     const std::vector<u8>& GetData() const { | ||||
|         return data; | ||||
|     std::vector<s16>& Samples() { | ||||
|         return samples; | ||||
|     } | ||||
|  | ||||
|     /// Returns the raw audio data for the buffer | ||||
|     const std::vector<s16>& GetSamples() const { | ||||
|         return samples; | ||||
|     } | ||||
|  | ||||
|     /// Returns the buffer tag, this is provided by the game to the audout service | ||||
| @@ -32,7 +37,7 @@ public: | ||||
|  | ||||
| private: | ||||
|     Tag tag; | ||||
|     std::vector<u8> data; | ||||
|     std::vector<s16> samples; | ||||
| }; | ||||
|  | ||||
| using BufferPtr = std::shared_ptr<Buffer>; | ||||
|   | ||||
							
								
								
									
										77
									
								
								src/audio_core/codec.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								src/audio_core/codec.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | ||||
| // Copyright 2018 yuzu Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #include <algorithm> | ||||
|  | ||||
| #include "audio_core/codec.h" | ||||
|  | ||||
| namespace AudioCore::Codec { | ||||
|  | ||||
| std::vector<s16> DecodeADPCM(const u8* const data, size_t size, const ADPCM_Coeff& coeff, | ||||
|                              ADPCMState& state) { | ||||
|     // GC-ADPCM with scale factor and variable coefficients. | ||||
|     // Frames are 8 bytes long containing 14 samples each. | ||||
|     // Samples are 4 bits (one nibble) long. | ||||
|  | ||||
|     constexpr size_t FRAME_LEN = 8; | ||||
|     constexpr size_t SAMPLES_PER_FRAME = 14; | ||||
|     constexpr std::array<int, 16> SIGNED_NIBBLES = { | ||||
|         {0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1}}; | ||||
|  | ||||
|     const size_t sample_count = (size / FRAME_LEN) * SAMPLES_PER_FRAME; | ||||
|     const size_t ret_size = | ||||
|         sample_count % 2 == 0 ? sample_count : sample_count + 1; // Ensure multiple of two. | ||||
|     std::vector<s16> ret(ret_size); | ||||
|  | ||||
|     int yn1 = state.yn1, yn2 = state.yn2; | ||||
|  | ||||
|     const size_t NUM_FRAMES = | ||||
|         (sample_count + (SAMPLES_PER_FRAME - 1)) / SAMPLES_PER_FRAME; // Round up. | ||||
|     for (size_t framei = 0; framei < NUM_FRAMES; framei++) { | ||||
|         const int frame_header = data[framei * FRAME_LEN]; | ||||
|         const int scale = 1 << (frame_header & 0xF); | ||||
|         const int idx = (frame_header >> 4) & 0x7; | ||||
|  | ||||
|         // Coefficients are fixed point with 11 bits fractional part. | ||||
|         const int coef1 = coeff[idx * 2 + 0]; | ||||
|         const int coef2 = coeff[idx * 2 + 1]; | ||||
|  | ||||
|         // Decodes an audio sample. One nibble produces one sample. | ||||
|         const auto decode_sample = [&](const int nibble) -> s16 { | ||||
|             const int xn = nibble * scale; | ||||
|             // We first transform everything into 11 bit fixed point, perform the second order | ||||
|             // digital filter, then transform back. | ||||
|             // 0x400 == 0.5 in 11 bit fixed point. | ||||
|             // Filter: y[n] = x[n] + 0.5 + c1 * y[n-1] + c2 * y[n-2] | ||||
|             int val = ((xn << 11) + 0x400 + coef1 * yn1 + coef2 * yn2) >> 11; | ||||
|             // Clamp to output range. | ||||
|             val = std::clamp<s32>(val, -32768, 32767); | ||||
|             // Advance output feedback. | ||||
|             yn2 = yn1; | ||||
|             yn1 = val; | ||||
|             return static_cast<s16>(val); | ||||
|         }; | ||||
|  | ||||
|         size_t outputi = framei * SAMPLES_PER_FRAME; | ||||
|         size_t datai = framei * FRAME_LEN + 1; | ||||
|         for (size_t i = 0; i < SAMPLES_PER_FRAME && outputi < sample_count; i += 2) { | ||||
|             const s16 sample1 = decode_sample(SIGNED_NIBBLES[data[datai] >> 4]); | ||||
|             ret[outputi] = sample1; | ||||
|             outputi++; | ||||
|  | ||||
|             const s16 sample2 = decode_sample(SIGNED_NIBBLES[data[datai] & 0xF]); | ||||
|             ret[outputi] = sample2; | ||||
|             outputi++; | ||||
|  | ||||
|             datai++; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     state.yn1 = yn1; | ||||
|     state.yn2 = yn2; | ||||
|  | ||||
|     return ret; | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore::Codec | ||||
							
								
								
									
										44
									
								
								src/audio_core/codec.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/audio_core/codec.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| // Copyright 2018 yuzu Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <array> | ||||
| #include <vector> | ||||
|  | ||||
| #include "common/common_types.h" | ||||
|  | ||||
| namespace AudioCore::Codec { | ||||
|  | ||||
| enum class PcmFormat : u32 { | ||||
|     Invalid = 0, | ||||
|     Int8 = 1, | ||||
|     Int16 = 2, | ||||
|     Int24 = 3, | ||||
|     Int32 = 4, | ||||
|     PcmFloat = 5, | ||||
|     Adpcm = 6, | ||||
| }; | ||||
|  | ||||
| /// See: Codec::DecodeADPCM | ||||
| struct ADPCMState { | ||||
|     // Two historical samples from previous processed buffer, | ||||
|     // required for ADPCM decoding | ||||
|     s16 yn1; ///< y[n-1] | ||||
|     s16 yn2; ///< y[n-2] | ||||
| }; | ||||
|  | ||||
| using ADPCM_Coeff = std::array<s16, 16>; | ||||
|  | ||||
| /** | ||||
|  * @param data Pointer to buffer that contains ADPCM data to decode | ||||
|  * @param size Size of buffer in bytes | ||||
|  * @param coeff ADPCM coefficients | ||||
|  * @param state ADPCM state, this is updated with new state | ||||
|  * @return Decoded stereo signed PCM16 data, sample_count in length | ||||
|  */ | ||||
| std::vector<s16> DecodeADPCM(const u8* const data, size_t size, const ADPCM_Coeff& coeff, | ||||
|                              ADPCMState& state); | ||||
|  | ||||
| }; // namespace AudioCore::Codec | ||||
| @@ -13,20 +13,30 @@ namespace AudioCore { | ||||
|  | ||||
| class SinkStreamImpl final : public SinkStream { | ||||
| public: | ||||
|     SinkStreamImpl(cubeb* ctx, cubeb_devid output_device) : ctx{ctx} { | ||||
|         cubeb_stream_params params; | ||||
|         params.rate = 48000; | ||||
|         params.channels = GetNumChannels(); | ||||
|         params.format = CUBEB_SAMPLE_S16NE; | ||||
|         params.layout = CUBEB_LAYOUT_STEREO; | ||||
|     SinkStreamImpl(cubeb* ctx, u32 sample_rate, u32 num_channels_, cubeb_devid output_device, | ||||
|                    const std::string& name) | ||||
|         : ctx{ctx}, num_channels{num_channels_} { | ||||
|  | ||||
|         u32 minimum_latency = 0; | ||||
|         if (num_channels == 6) { | ||||
|             // 6-channel audio does not seem to work with cubeb + SDL, so we downsample this to 2 | ||||
|             // channel for now | ||||
|             is_6_channel = true; | ||||
|             num_channels = 2; | ||||
|         } | ||||
|  | ||||
|         cubeb_stream_params params{}; | ||||
|         params.rate = sample_rate; | ||||
|         params.channels = num_channels; | ||||
|         params.format = CUBEB_SAMPLE_S16NE; | ||||
|         params.layout = num_channels == 1 ? CUBEB_LAYOUT_MONO : CUBEB_LAYOUT_STEREO; | ||||
|  | ||||
|         u32 minimum_latency{}; | ||||
|         if (cubeb_get_min_latency(ctx, ¶ms, &minimum_latency) != CUBEB_OK) { | ||||
|             LOG_CRITICAL(Audio_Sink, "Error getting minimum latency"); | ||||
|         } | ||||
|  | ||||
|         if (cubeb_stream_init(ctx, &stream_backend, "yuzu Audio Output", nullptr, nullptr, | ||||
|                               output_device, ¶ms, std::max(512u, minimum_latency), | ||||
|         if (cubeb_stream_init(ctx, &stream_backend, name.c_str(), nullptr, nullptr, output_device, | ||||
|                               ¶ms, std::max(512u, minimum_latency), | ||||
|                               &SinkStreamImpl::DataCallback, &SinkStreamImpl::StateCallback, | ||||
|                               this) != CUBEB_OK) { | ||||
|             LOG_CRITICAL(Audio_Sink, "Error initializing cubeb stream"); | ||||
| @@ -51,33 +61,29 @@ public: | ||||
|         cubeb_stream_destroy(stream_backend); | ||||
|     } | ||||
|  | ||||
|     void EnqueueSamples(u32 num_channels, const s16* samples, size_t sample_count) override { | ||||
|     void EnqueueSamples(u32 num_channels, const std::vector<s16>& samples) override { | ||||
|         if (!ctx) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         queue.reserve(queue.size() + sample_count * GetNumChannels()); | ||||
|         queue.reserve(queue.size() + samples.size() * GetNumChannels()); | ||||
|  | ||||
|         if (num_channels == 2) { | ||||
|             // Copy as-is | ||||
|             std::copy(samples, samples + sample_count * GetNumChannels(), | ||||
|                       std::back_inserter(queue)); | ||||
|         } else if (num_channels == 6) { | ||||
|         if (is_6_channel) { | ||||
|             // Downsample 6 channels to 2 | ||||
|             const size_t sample_count_copy_size = sample_count * num_channels * 2; | ||||
|             const size_t sample_count_copy_size = samples.size() * 2; | ||||
|             queue.reserve(sample_count_copy_size); | ||||
|             for (size_t i = 0; i < sample_count * num_channels; i += num_channels) { | ||||
|             for (size_t i = 0; i < samples.size(); i += num_channels) { | ||||
|                 queue.push_back(samples[i]); | ||||
|                 queue.push_back(samples[i + 1]); | ||||
|             } | ||||
|         } else { | ||||
|             ASSERT_MSG(false, "Unimplemented"); | ||||
|             // Copy as-is | ||||
|             std::copy(samples.begin(), samples.end(), std::back_inserter(queue)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     u32 GetNumChannels() const { | ||||
|         // Only support 2-channel stereo output for now | ||||
|         return 2; | ||||
|         return num_channels; | ||||
|     } | ||||
|  | ||||
| private: | ||||
| @@ -85,6 +91,8 @@ private: | ||||
|  | ||||
|     cubeb* ctx{}; | ||||
|     cubeb_stream* stream_backend{}; | ||||
|     u32 num_channels{}; | ||||
|     bool is_6_channel{}; | ||||
|  | ||||
|     std::vector<s16> queue; | ||||
|  | ||||
| @@ -129,8 +137,10 @@ CubebSink::~CubebSink() { | ||||
|     cubeb_destroy(ctx); | ||||
| } | ||||
|  | ||||
| SinkStream& CubebSink::AcquireSinkStream(u32 sample_rate, u32 num_channels) { | ||||
|     sink_streams.push_back(std::make_unique<SinkStreamImpl>(ctx, output_device)); | ||||
| SinkStream& CubebSink::AcquireSinkStream(u32 sample_rate, u32 num_channels, | ||||
|                                          const std::string& name) { | ||||
|     sink_streams.push_back( | ||||
|         std::make_unique<SinkStreamImpl>(ctx, sample_rate, num_channels, output_device, name)); | ||||
|     return *sink_streams.back(); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -18,7 +18,8 @@ public: | ||||
|     explicit CubebSink(std::string device_id); | ||||
|     ~CubebSink() override; | ||||
|  | ||||
|     SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels) override; | ||||
|     SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels, | ||||
|                                   const std::string& name) override; | ||||
|  | ||||
| private: | ||||
|     cubeb* ctx{}; | ||||
|   | ||||
| @@ -13,14 +13,14 @@ public: | ||||
|     explicit NullSink(std::string){}; | ||||
|     ~NullSink() override = default; | ||||
|  | ||||
|     SinkStream& AcquireSinkStream(u32 /*sample_rate*/, u32 /*num_channels*/) override { | ||||
|     SinkStream& AcquireSinkStream(u32 /*sample_rate*/, u32 /*num_channels*/, | ||||
|                                   const std::string& /*name*/) override { | ||||
|         return null_sink_stream; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     struct NullSinkStreamImpl final : SinkStream { | ||||
|         void EnqueueSamples(u32 /*num_channels*/, const s16* /*samples*/, | ||||
|                             size_t /*sample_count*/) override {} | ||||
|         void EnqueueSamples(u32 /*num_channels*/, const std::vector<s16>& /*samples*/) override {} | ||||
|     } null_sink_stream; | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -5,6 +5,7 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <memory> | ||||
| #include <string> | ||||
|  | ||||
| #include "audio_core/sink_stream.h" | ||||
| #include "common/common_types.h" | ||||
| @@ -21,7 +22,8 @@ constexpr char auto_device_name[] = "auto"; | ||||
| class Sink { | ||||
| public: | ||||
|     virtual ~Sink() = default; | ||||
|     virtual SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels) = 0; | ||||
|     virtual SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels, | ||||
|                                           const std::string& name) = 0; | ||||
| }; | ||||
|  | ||||
| using SinkPtr = std::unique_ptr<Sink>; | ||||
|   | ||||
| @@ -5,6 +5,7 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <memory> | ||||
| #include <vector> | ||||
|  | ||||
| #include "common/common_types.h" | ||||
|  | ||||
| @@ -22,9 +23,8 @@ public: | ||||
|      * Feed stereo samples to sink. | ||||
|      * @param num_channels Number of channels used. | ||||
|      * @param samples Samples in interleaved stereo PCM16 format. | ||||
|      * @param sample_count Number of samples. | ||||
|      */ | ||||
|     virtual void EnqueueSamples(u32 num_channels, const s16* samples, size_t sample_count) = 0; | ||||
|     virtual void EnqueueSamples(u32 num_channels, const std::vector<s16>& samples) = 0; | ||||
| }; | ||||
|  | ||||
| using SinkStreamPtr = std::unique_ptr<SinkStream>; | ||||
|   | ||||
| @@ -32,17 +32,13 @@ u32 Stream::GetNumChannels() const { | ||||
|     return {}; | ||||
| } | ||||
|  | ||||
| u32 Stream::GetSampleSize() const { | ||||
|     return GetNumChannels() * 2; | ||||
| } | ||||
|  | ||||
| Stream::Stream(u32 sample_rate, Format format, ReleaseCallback&& release_callback, | ||||
|                SinkStream& sink_stream) | ||||
|                SinkStream& sink_stream, std::string&& name_) | ||||
|     : sample_rate{sample_rate}, format{format}, release_callback{std::move(release_callback)}, | ||||
|       sink_stream{sink_stream} { | ||||
|       sink_stream{sink_stream}, name{std::move(name_)} { | ||||
|  | ||||
|     release_event = CoreTiming::RegisterEvent( | ||||
|         "Stream::Release", [this](u64 userdata, int cycles_late) { ReleaseActiveBuffer(); }); | ||||
|         name, [this](u64 userdata, int cycles_late) { ReleaseActiveBuffer(); }); | ||||
| } | ||||
|  | ||||
| void Stream::Play() { | ||||
| @@ -55,17 +51,15 @@ void Stream::Stop() { | ||||
| } | ||||
|  | ||||
| s64 Stream::GetBufferReleaseCycles(const Buffer& buffer) const { | ||||
|     const size_t num_samples{buffer.GetData().size() / GetSampleSize()}; | ||||
|     const size_t num_samples{buffer.GetSamples().size() / GetNumChannels()}; | ||||
|     return CoreTiming::usToCycles((static_cast<u64>(num_samples) * 1000000) / sample_rate); | ||||
| } | ||||
|  | ||||
| static std::vector<s16> GetVolumeAdjustedSamples(const std::vector<u8>& data) { | ||||
|     std::vector<s16> samples(data.size() / sizeof(s16)); | ||||
|     std::memcpy(samples.data(), data.data(), data.size()); | ||||
| static void VolumeAdjustSamples(std::vector<s16>& samples) { | ||||
|     const float volume{std::clamp(Settings::values.volume, 0.0f, 1.0f)}; | ||||
|  | ||||
|     if (volume == 1.0f) { | ||||
|         return samples; | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     // Implementation of a volume slider with a dynamic range of 60 dB | ||||
| @@ -73,8 +67,6 @@ static std::vector<s16> GetVolumeAdjustedSamples(const std::vector<u8>& data) { | ||||
|     for (auto& sample : samples) { | ||||
|         sample = static_cast<s16>(sample * volume_scale_factor); | ||||
|     } | ||||
|  | ||||
|     return samples; | ||||
| } | ||||
|  | ||||
| void Stream::PlayNextBuffer() { | ||||
| @@ -96,14 +88,14 @@ void Stream::PlayNextBuffer() { | ||||
|     active_buffer = queued_buffers.front(); | ||||
|     queued_buffers.pop(); | ||||
|  | ||||
|     const size_t sample_count{active_buffer->GetData().size() / GetSampleSize()}; | ||||
|     sink_stream.EnqueueSamples( | ||||
|         GetNumChannels(), GetVolumeAdjustedSamples(active_buffer->GetData()).data(), sample_count); | ||||
|     VolumeAdjustSamples(active_buffer->Samples()); | ||||
|     sink_stream.EnqueueSamples(GetNumChannels(), active_buffer->GetSamples()); | ||||
|  | ||||
|     CoreTiming::ScheduleEventThreadsafe(GetBufferReleaseCycles(*active_buffer), release_event, {}); | ||||
| } | ||||
|  | ||||
| void Stream::ReleaseActiveBuffer() { | ||||
|     ASSERT(active_buffer); | ||||
|     released_buffers.push(std::move(active_buffer)); | ||||
|     release_callback(); | ||||
|     PlayNextBuffer(); | ||||
|   | ||||
| @@ -6,6 +6,7 @@ | ||||
|  | ||||
| #include <functional> | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include <vector> | ||||
| #include <queue> | ||||
|  | ||||
| @@ -33,7 +34,7 @@ public: | ||||
|     using ReleaseCallback = std::function<void()>; | ||||
|  | ||||
|     Stream(u32 sample_rate, Format format, ReleaseCallback&& release_callback, | ||||
|            SinkStream& sink_stream); | ||||
|            SinkStream& sink_stream, std::string&& name_); | ||||
|  | ||||
|     /// Plays the audio stream | ||||
|     void Play(); | ||||
| @@ -68,9 +69,6 @@ public: | ||||
|     /// Gets the number of channels | ||||
|     u32 GetNumChannels() const; | ||||
|  | ||||
|     /// Gets the sample size in bytes | ||||
|     u32 GetSampleSize() const; | ||||
|  | ||||
| private: | ||||
|     /// Current state of the stream | ||||
|     enum class State { | ||||
| @@ -96,6 +94,7 @@ private: | ||||
|     std::queue<BufferPtr> queued_buffers;   ///< Buffers queued to be played in the stream | ||||
|     std::queue<BufferPtr> released_buffers; ///< Buffers recently released from the stream | ||||
|     SinkStream& sink_stream;                ///< Output sink for the stream | ||||
|     std::string name;                       ///< Name of the stream, must be unique | ||||
| }; | ||||
|  | ||||
| using StreamPtr = std::shared_ptr<Stream>; | ||||
|   | ||||
| @@ -4,6 +4,8 @@ | ||||
|  | ||||
| #include <array> | ||||
| #include <vector> | ||||
|  | ||||
| #include "audio_core/codec.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "core/core.h" | ||||
| #include "core/hle/ipc_helpers.h" | ||||
| @@ -48,7 +50,7 @@ public: | ||||
|         buffer_event = Kernel::Event::Create(Kernel::ResetType::Sticky, "IAudioOutBufferReleased"); | ||||
|  | ||||
|         stream = audio_core.OpenStream(audio_params.sample_rate, audio_params.channel_count, | ||||
|                                        [=]() { buffer_event->Signal(); }); | ||||
|                                        "IAudioOut", [=]() { buffer_event->Signal(); }); | ||||
|     } | ||||
|  | ||||
| private: | ||||
| @@ -111,10 +113,10 @@ private: | ||||
|         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()); | ||||
|         std::vector<s16> 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(data))) { | ||||
|         if (!audio_core.QueueBuffer(stream, tag, std::move(samples))) { | ||||
|             IPC::ResponseBuilder rb{ctx, 2}; | ||||
|             rb.Push(ResultCode(ErrorModule::Audio, ErrCodes::BufferCountExceeded)); | ||||
|         } | ||||
| @@ -200,7 +202,7 @@ void AudOutU::OpenAudioOutImpl(Kernel::HLERequestContext& ctx) { | ||||
|     rb.Push(RESULT_SUCCESS); | ||||
|     rb.Push<u32>(DefaultSampleRate); | ||||
|     rb.Push<u32>(params.channel_count); | ||||
|     rb.Push<u32>(static_cast<u32>(PcmFormat::Int16)); | ||||
|     rb.Push<u32>(static_cast<u32>(AudioCore::Codec::PcmFormat::Int16)); | ||||
|     rb.Push<u32>(static_cast<u32>(AudioState::Stopped)); | ||||
|     rb.PushIpcInterface<Audio::IAudioOut>(audio_out_interface); | ||||
| } | ||||
|   | ||||
| @@ -38,16 +38,6 @@ private: | ||||
|  | ||||
|     void ListAudioOutsImpl(Kernel::HLERequestContext& ctx); | ||||
|     void OpenAudioOutImpl(Kernel::HLERequestContext& ctx); | ||||
|  | ||||
|     enum class PcmFormat : u32 { | ||||
|         Invalid = 0, | ||||
|         Int8 = 1, | ||||
|         Int16 = 2, | ||||
|         Int24 = 3, | ||||
|         Int32 = 4, | ||||
|         PcmFloat = 5, | ||||
|         Adpcm = 6, | ||||
|     }; | ||||
| }; | ||||
|  | ||||
| } // namespace Service::Audio | ||||
|   | ||||
| @@ -15,13 +15,10 @@ | ||||
|  | ||||
| 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> { | ||||
| public: | ||||
|     explicit IAudioRenderer(AudioRendererParameter audren_params) | ||||
|         : ServiceFramework("IAudioRenderer"), worker_params(audren_params) { | ||||
|     explicit IAudioRenderer(AudioCore::AudioRendererParameter audren_params) | ||||
|         : ServiceFramework("IAudioRenderer") { | ||||
|         static const FunctionInfo functions[] = { | ||||
|             {0, nullptr, "GetAudioRendererSampleRate"}, | ||||
|             {1, nullptr, "GetAudioRendererSampleCount"}, | ||||
| @@ -39,21 +36,8 @@ public: | ||||
|         RegisterHandlers(functions); | ||||
|  | ||||
|         system_event = | ||||
|             Kernel::Event::Create(Kernel::ResetType::OneShot, "IAudioRenderer:SystemEvent"); | ||||
|  | ||||
|         // 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); | ||||
|             Kernel::Event::Create(Kernel::ResetType::Sticky, "IAudioRenderer:SystemEvent"); | ||||
|         renderer = std::make_unique<AudioCore::AudioRenderer>(audren_params, system_event); | ||||
|     } | ||||
|  | ||||
| private: | ||||
| @@ -62,60 +46,9 @@ private: | ||||
|     } | ||||
|  | ||||
|     void RequestUpdateAudioRenderer(Kernel::HLERequestContext& ctx) { | ||||
|         UpdateDataHeader config{}; | ||||
|         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); | ||||
|  | ||||
|         ctx.WriteBuffer(renderer->UpdateAudioRenderer(ctx.ReadBuffer())); | ||||
|         IPC::ResponseBuilder rb{ctx, 2}; | ||||
|         rb.Push(RESULT_SUCCESS); | ||||
|  | ||||
|         LOG_WARNING(Service_Audio, "(STUBBED) called"); | ||||
|     } | ||||
|  | ||||
| @@ -136,8 +69,6 @@ private: | ||||
|     } | ||||
|  | ||||
|     void QuerySystemEvent(Kernel::HLERequestContext& ctx) { | ||||
|         // system_event->Signal(); | ||||
|  | ||||
|         IPC::ResponseBuilder rb{ctx, 2, 1}; | ||||
|         rb.Push(RESULT_SUCCESS); | ||||
|         rb.PushCopyObjects(system_event); | ||||
| @@ -145,131 +76,8 @@ private: | ||||
|         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; | ||||
|     AudioRendererParameter worker_params; | ||||
|     std::vector<VoiceOutStatus> voice_status_list; | ||||
|     std::unique_ptr<AudioCore::AudioRenderer> renderer; | ||||
| }; | ||||
|  | ||||
| class IAudioDevice final : public ServiceFramework<IAudioDevice> { | ||||
| @@ -368,7 +176,7 @@ AudRenU::AudRenU() : ServiceFramework("audren:u") { | ||||
|  | ||||
| void AudRenU::OpenAudioRenderer(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp{ctx}; | ||||
|     auto params = rp.PopRaw<AudioRendererParameter>(); | ||||
|     auto params = rp.PopRaw<AudioCore::AudioRendererParameter>(); | ||||
|     IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | ||||
|  | ||||
|     rb.Push(RESULT_SUCCESS); | ||||
| @@ -379,7 +187,7 @@ void AudRenU::OpenAudioRenderer(Kernel::HLERequestContext& ctx) { | ||||
|  | ||||
| void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& 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); | ||||
|     buffer_sz += params.unknown_c * 1024; | ||||
|   | ||||
| @@ -4,6 +4,7 @@ | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "audio_core/audio_renderer.h" | ||||
| #include "core/hle/service/service.h" | ||||
|  | ||||
| namespace Kernel { | ||||
| @@ -12,24 +13,6 @@ class HLERequestContext; | ||||
|  | ||||
| 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> { | ||||
| public: | ||||
|     explicit AudRenU(); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user