summaryrefslogtreecommitdiffstats
path: root/src/audio_core
diff options
context:
space:
mode:
Diffstat (limited to 'src/audio_core')
-rw-r--r--src/audio_core/CMakeLists.txt4
-rw-r--r--src/audio_core/audio_out.cpp10
-rw-r--r--src/audio_core/audio_out.h5
-rw-r--r--src/audio_core/audio_renderer.cpp234
-rw-r--r--src/audio_core/audio_renderer.h206
-rw-r--r--src/audio_core/buffer.h13
-rw-r--r--src/audio_core/codec.cpp77
-rw-r--r--src/audio_core/codec.h44
-rw-r--r--src/audio_core/cubeb_sink.cpp54
-rw-r--r--src/audio_core/cubeb_sink.h3
-rw-r--r--src/audio_core/null_sink.h6
-rw-r--r--src/audio_core/sink.h4
-rw-r--r--src/audio_core/sink_stream.h4
-rw-r--r--src/audio_core/stream.cpp26
-rw-r--r--src/audio_core/stream.h7
15 files changed, 636 insertions, 61 deletions
diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt
index 81121167d..827ab0ac7 100644
--- a/src/audio_core/CMakeLists.txt
+++ b/src/audio_core/CMakeLists.txt
@@ -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
diff --git a/src/audio_core/audio_out.cpp b/src/audio_core/audio_out.cpp
index 3dfdf61f9..12632a95c 100644
--- a/src/audio_core/audio_out.cpp
+++ b/src/audio_core/audio_out.cpp
@@ -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)));
}
diff --git a/src/audio_core/audio_out.h b/src/audio_core/audio_out.h
index 95e9b53fe..39b7e656b 100644
--- a/src/audio_core/audio_out.h
+++ b/src/audio_core/audio_out.h
@@ -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;
diff --git a/src/audio_core/audio_renderer.cpp b/src/audio_core/audio_renderer.cpp
new file mode 100644
index 000000000..282f345c5
--- /dev/null
+++ b/src/audio_core/audio_renderer.cpp
@@ -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
diff --git a/src/audio_core/audio_renderer.h b/src/audio_core/audio_renderer.h
new file mode 100644
index 000000000..6950a4681
--- /dev/null
+++ b/src/audio_core/audio_renderer.h
@@ -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
diff --git a/src/audio_core/buffer.h b/src/audio_core/buffer.h
index 4bf5fd58a..a323b23ec 100644
--- a/src/audio_core/buffer.h
+++ b/src/audio_core/buffer.h
@@ -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>;
diff --git a/src/audio_core/codec.cpp b/src/audio_core/codec.cpp
new file mode 100644
index 000000000..c3021403f
--- /dev/null
+++ b/src/audio_core/codec.cpp
@@ -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
diff --git a/src/audio_core/codec.h b/src/audio_core/codec.h
new file mode 100644
index 000000000..3f845c42c
--- /dev/null
+++ b/src/audio_core/codec.h
@@ -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
diff --git a/src/audio_core/cubeb_sink.cpp b/src/audio_core/cubeb_sink.cpp
index 34ae5b062..1501ef1f4 100644
--- a/src/audio_core/cubeb_sink.cpp
+++ b/src/audio_core/cubeb_sink.cpp
@@ -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();
+ SinkStreamImpl(cubeb* ctx, u32 sample_rate, u32 num_channels_, cubeb_devid output_device,
+ const std::string& name)
+ : ctx{ctx}, num_channels{num_channels_} {
+
+ 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 = CUBEB_LAYOUT_STEREO;
+ params.layout = num_channels == 1 ? CUBEB_LAYOUT_MONO : CUBEB_LAYOUT_STEREO;
- u32 minimum_latency = 0;
+ u32 minimum_latency{};
if (cubeb_get_min_latency(ctx, &params, &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, &params, std::max(512u, minimum_latency),
+ if (cubeb_stream_init(ctx, &stream_backend, name.c_str(), nullptr, nullptr, output_device,
+ &params, 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();
}
diff --git a/src/audio_core/cubeb_sink.h b/src/audio_core/cubeb_sink.h
index d07113f1f..59cbf05e9 100644
--- a/src/audio_core/cubeb_sink.h
+++ b/src/audio_core/cubeb_sink.h
@@ -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{};
diff --git a/src/audio_core/null_sink.h b/src/audio_core/null_sink.h
index 2e04438f7..f235d93e5 100644
--- a/src/audio_core/null_sink.h
+++ b/src/audio_core/null_sink.h
@@ -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;
};
diff --git a/src/audio_core/sink.h b/src/audio_core/sink.h
index d1bb98c3d..95c7b2b6e 100644
--- a/src/audio_core/sink.h
+++ b/src/audio_core/sink.h
@@ -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>;
diff --git a/src/audio_core/sink_stream.h b/src/audio_core/sink_stream.h
index e7a3f01b0..41b6736d8 100644
--- a/src/audio_core/sink_stream.h
+++ b/src/audio_core/sink_stream.h
@@ -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>;
diff --git a/src/audio_core/stream.cpp b/src/audio_core/stream.cpp
index a0045b7a1..ad9e2915c 100644
--- a/src/audio_core/stream.cpp
+++ b/src/audio_core/stream.cpp
@@ -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();
diff --git a/src/audio_core/stream.h b/src/audio_core/stream.h
index 35253920e..049b92ca9 100644
--- a/src/audio_core/stream.h
+++ b/src/audio_core/stream.h
@@ -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>;