From 7fccc995ce2fa191025a6860ac30c66557edf893 Mon Sep 17 00:00:00 2001 From: James Rowe Date: Wed, 27 Feb 2019 23:13:51 +0100 Subject: [PATCH 01/11] Initial Mic setup --- src/audio_core/CMakeLists.txt | 4 + src/audio_core/cubeb_input.cpp | 166 ++++++++++++++++++ src/audio_core/cubeb_input.h | 31 ++++ src/audio_core/cubeb_sink.cpp | 4 +- src/citra_qt/configuration/config.cpp | 5 + .../configuration/configure_audio.cpp | 38 +++- src/citra_qt/configuration/configure_audio.h | 3 +- src/citra_qt/configuration/configure_audio.ui | 56 +++++- src/core/CMakeLists.txt | 2 + src/core/frontend/mic.cpp | 23 +++ src/core/frontend/mic.h | 110 ++++++++++++ src/core/hle/service/mic_u.cpp | 129 +++++++++----- src/core/hle/service/mic_u.h | 2 + src/core/settings.cpp | 12 ++ src/core/settings.h | 2 + 15 files changed, 528 insertions(+), 59 deletions(-) create mode 100644 src/audio_core/cubeb_input.cpp create mode 100644 src/audio_core/cubeb_input.h create mode 100644 src/core/frontend/mic.cpp create mode 100644 src/core/frontend/mic.h diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index 27e7d723b..fc298323f 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -2,6 +2,10 @@ add_library(audio_core STATIC audio_types.h codec.cpp codec.h + cubeb_input.cpp + cubeb_input.h + cubeb_sink.cpp + cubeb_sink.h dsp_interface.cpp dsp_interface.h hle/adts.h diff --git a/src/audio_core/cubeb_input.cpp b/src/audio_core/cubeb_input.cpp new file mode 100644 index 000000000..691c2b9c4 --- /dev/null +++ b/src/audio_core/cubeb_input.cpp @@ -0,0 +1,166 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include "audio_core/cubeb_input.h" +#include "common/logging/log.h" + +namespace AudioCore { + +struct CubebInput::Impl { + // unsigned int sample_rate = 0; + // std::vector device_list; + + cubeb* ctx = nullptr; + cubeb_stream* stream = nullptr; + + bool looped_buffer; + u8* buffer; + u32 buffer_size; + u32 initial_offset; + u32 offset; + u32 audio_buffer_size; + + static long DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer, + void* output_buffer, long num_frames); + static void StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state); +}; + +CubebInput::CubebInput() : impl(std::make_unique()) { + if (cubeb_init(&impl->ctx, "Citra Input", nullptr) != CUBEB_OK) { + LOG_ERROR(Audio, "cubeb_init failed! Mic will not work properly"); + return; + } +} + +CubebInput::~CubebInput() { + if (!impl->ctx) + return; + + if (cubeb_stream_stop(impl->stream) != CUBEB_OK) { + LOG_ERROR(Audio, "Error stopping cubeb input stream."); + } + + cubeb_destroy(impl->ctx); +} + +void CubebInput::StartSampling(Frontend::Mic::Parameters params) { + // Cubeb apparently only supports signed 16 bit PCM (and float32 which the 3ds doesn't support) + // TODO resample the input stream + if (params.sign == Frontend::Mic::Signedness::Unsigned) { + LOG_ERROR(Audio, + "Application requested unsupported unsigned pcm format. Falling back to signed"); + } + if (params.sample_size != 16) { + LOG_ERROR(Audio, + "Application requested unsupported 8 bit pcm format. Falling back to 16 bits"); + } + + impl->buffer = backing_memory; + impl->buffer_size = backing_memory_size; + impl->audio_buffer_size = params.buffer_size; + impl->offset = params.buffer_offset; + impl->looped_buffer = params.buffer_loop; + + cubeb_devid input_device = nullptr; + cubeb_stream_params input_params; + input_params.channels = 1; + input_params.layout = CUBEB_LAYOUT_UNDEFINED; + input_params.prefs = CUBEB_STREAM_PREF_NONE; + input_params.format = CUBEB_SAMPLE_S16LE; + input_params.rate = params.sample_rate; + + u32 latency_frames; + if (cubeb_get_min_latency(impl->ctx, &input_params, &latency_frames) != CUBEB_OK) { + LOG_ERROR(Audio, "Could not get minimum latency"); + } + + if (cubeb_stream_init(impl->ctx, &impl->stream, "Citra Microphone", input_device, &input_params, + nullptr, nullptr, latency_frames, Impl::DataCallback, Impl::StateCallback, + impl.get()) != CUBEB_OK) { + LOG_CRITICAL(Audio, "Error creating cubeb input stream"); + } + + cubeb_stream_start(impl->stream); +} + +void CubebInput::StopSampling() { + if (impl->stream) { + cubeb_stream_stop(impl->stream); + } +} + +void CubebInput::AdjustSampleRate(u32 sample_rate) { + // TODO This should restart the stream with the new sample rate + LOG_ERROR(Audio, "AdjustSampleRate unimplemented!"); +} + +long CubebInput::Impl::DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer, + void* output_buffer, long num_frames) { + Impl* impl = static_cast(user_data); + const u8* data = reinterpret_cast(input_buffer); + + if (!impl) { + return 0; + } + + if (!impl->buffer) { + return 0; + } + + u64 total_written = 0; + u64 to_write = num_frames; + u64 remaining_space = impl->audio_buffer_size - impl->offset; + if (to_write > remaining_space) { + to_write = remaining_space; + } + std::memcpy(impl->buffer + impl->offset, data, to_write); + impl->offset += to_write; + total_written += to_write; + + if (impl->looped_buffer && num_frames > total_written) { + impl->offset = impl->initial_offset; + to_write = num_frames - to_write; + std::memcpy(impl->buffer + impl->offset, data, to_write); + impl->offset += to_write; + total_written += to_write; + } + // The last 4 bytes of the shared memory contains the latest offset + // so update that as well https://www.3dbrew.org/wiki/MIC_Shared_Memory + std::memcpy(impl->buffer + (impl->buffer_size - sizeof(u32)), + reinterpret_cast(&impl->offset), sizeof(u32)); + + // returning less than num_frames here signals cubeb to stop sampling + return total_written; +} + +void CubebInput::Impl::StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state) {} + +std::vector ListCubebInputDevices() { + std::vector device_list; + cubeb* ctx; + + if (cubeb_init(&ctx, "Citra Input Device Enumerator", nullptr) != CUBEB_OK) { + LOG_CRITICAL(Audio, "cubeb_init failed"); + return {}; + } + + cubeb_device_collection collection; + if (cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_INPUT, &collection) != CUBEB_OK) { + LOG_WARNING(Audio_Sink, "Audio input device enumeration not supported"); + } else { + for (size_t i = 0; i < collection.count; i++) { + const cubeb_device_info& device = collection.device[i]; + if (device.state == CUBEB_DEVICE_STATE_ENABLED && device.friendly_name) { + device_list.emplace_back(device.friendly_name); + } + } + cubeb_device_collection_destroy(ctx, &collection); + } + + cubeb_destroy(ctx); + return device_list; +} +} // namespace AudioCore diff --git a/src/audio_core/cubeb_input.h b/src/audio_core/cubeb_input.h new file mode 100644 index 000000000..be764ba43 --- /dev/null +++ b/src/audio_core/cubeb_input.h @@ -0,0 +1,31 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include "core/frontend/mic.h" + +namespace AudioCore { + +class CubebInput final : public Frontend::Mic::Interface { +public: + CubebInput(); + ~CubebInput(); + + void StartSampling(Frontend::Mic::Parameters params) override; + + void StopSampling() override; + + void AdjustSampleRate(u32 sample_rate) override; + +private: + struct Impl; + std::unique_ptr impl; +}; + +std::vector ListCubebInputDevices(); + +} // namespace AudioCore diff --git a/src/audio_core/cubeb_sink.cpp b/src/audio_core/cubeb_sink.cpp index 49bf3aaa9..a0b0eb944 100644 --- a/src/audio_core/cubeb_sink.cpp +++ b/src/audio_core/cubeb_sink.cpp @@ -27,7 +27,7 @@ struct CubebSink::Impl { }; CubebSink::CubebSink(std::string_view target_device_name) : impl(std::make_unique()) { - if (cubeb_init(&impl->ctx, "Citra", nullptr) != CUBEB_OK) { + if (cubeb_init(&impl->ctx, "Citra Output", nullptr) != CUBEB_OK) { LOG_CRITICAL(Audio_Sink, "cubeb_init failed"); return; } @@ -179,7 +179,7 @@ std::vector ListCubebSinkDevices() { } else { for (std::size_t i = 0; i < collection.count; i++) { const cubeb_device_info& device = collection.device[i]; - if (device.friendly_name) { + if (device.state == CUBEB_DEVICE_STATE_ENABLED && device.friendly_name) { device_list.emplace_back(device.friendly_name); } } diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index 201acafe4..29f61fae3 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -198,6 +198,9 @@ void Config::ReadValues() { Settings::values.audio_device_id = ReadSetting("output_device", "auto").toString().toStdString(); Settings::values.volume = ReadSetting("volume", 1).toFloat(); + Settings::values.mic_input_type = ReadSetting("mic_input_type", 0).toInt(); + Settings::values.mic_input_device = + ReadSetting("mic_input_device", "Default").toString().toStdString(); qt_config->endGroup(); using namespace Service::CAM; @@ -480,6 +483,8 @@ void Config::SaveValues() { WriteSetting("enable_audio_stretching", Settings::values.enable_audio_stretching, true); WriteSetting("output_device", QString::fromStdString(Settings::values.audio_device_id), "auto"); WriteSetting("volume", Settings::values.volume, 1.0f); + WriteSetting("mic_input_device", QString::fromStdString(Settings::values.mic_input_device), 0); + WriteSetting("mic_input_type", Settings::values.mic_input_type, "Default"); qt_config->endGroup(); using namespace Service::CAM; diff --git a/src/citra_qt/configuration/configure_audio.cpp b/src/citra_qt/configuration/configure_audio.cpp index 04211a161..1d9731cd3 100644 --- a/src/citra_qt/configuration/configure_audio.cpp +++ b/src/citra_qt/configuration/configure_audio.cpp @@ -3,6 +3,9 @@ // Refer to the license.txt file included. #include +#include +#include +#include "audio_core/cubeb_input.h" #include "audio_core/sink.h" #include "audio_core/sink_details.h" #include "citra_qt/configuration/configure_audio.h" @@ -28,10 +31,21 @@ ConfigureAudio::ConfigureAudio(QWidget* parent) connect(ui->volume_slider, &QSlider::valueChanged, this, &ConfigureAudio::setVolumeIndicatorText); + ui->input_device_combo_box->clear(); + ui->input_device_combo_box->addItem(tr("Default")); + for (const auto& device : AudioCore::ListCubebInputDevices()) { + ui->input_device_combo_box->addItem(QString::fromStdString(device)); + } + + connect(ui->input_type_combo_box, QOverload::of(&QComboBox::currentIndexChanged), this, + &ConfigureAudio::updateAudioInputDevices); + + ui->input_type_combo_box->setEnabled(!Core::System::GetInstance().IsPoweredOn()); + ui->input_device_combo_box->setEnabled(!Core::System::GetInstance().IsPoweredOn()); + this->setConfiguration(); - connect(ui->output_sink_combo_box, - static_cast(&QComboBox::currentIndexChanged), this, - &ConfigureAudio::updateAudioDevices); + connect(ui->output_sink_combo_box, QOverload::of(&QComboBox::currentIndexChanged), this, + &ConfigureAudio::updateAudioOutputDevices); } ConfigureAudio::~ConfigureAudio() {} @@ -40,7 +54,7 @@ void ConfigureAudio::setConfiguration() { setOutputSinkFromSinkID(); // The device list cannot be pre-populated (nor listed) until the output sink is known. - updateAudioDevices(ui->output_sink_combo_box->currentIndex()); + updateAudioOutputDevices(ui->output_sink_combo_box->currentIndex()); setAudioDeviceFromDeviceID(); @@ -59,6 +73,11 @@ void ConfigureAudio::setConfiguration() { selection = 0; } ui->emulation_combo_box->setCurrentIndex(selection); + + ui->input_type_combo_box->setCurrentIndex(Settings::values.mic_input_type); + ui->input_device_combo_box->setCurrentText( + QString::fromStdString(Settings::values.mic_input_device)); + updateAudioInputDevices(Settings::values.mic_input_type); } void ConfigureAudio::setOutputSinkFromSinkID() { @@ -105,9 +124,11 @@ void ConfigureAudio::applyConfiguration() { static_cast(ui->volume_slider->sliderPosition()) / ui->volume_slider->maximum(); Settings::values.enable_dsp_lle = ui->emulation_combo_box->currentIndex() != 0; Settings::values.enable_dsp_lle_multithread = ui->emulation_combo_box->currentIndex() == 2; + Settings::values.mic_input_type = ui->input_type_combo_box->currentIndex(); + Settings::values.mic_input_device = ui->input_device_combo_box->currentText().toStdString(); } -void ConfigureAudio::updateAudioDevices(int sink_index) { +void ConfigureAudio::updateAudioOutputDevices(int sink_index) { ui->audio_device_combo_box->clear(); ui->audio_device_combo_box->addItem(AudioCore::auto_device_name); @@ -117,6 +138,13 @@ void ConfigureAudio::updateAudioDevices(int sink_index) { } } +void ConfigureAudio::updateAudioInputDevices(int index) { + // TODO: Don't hardcode this to the index for "Real Device" without making it a constant + // somewhere + ui->input_device_combo_box->setEnabled(index == 1 && + !Core::System::GetInstance().IsPoweredOn()); +} + void ConfigureAudio::retranslateUi() { ui->retranslateUi(this); } diff --git a/src/citra_qt/configuration/configure_audio.h b/src/citra_qt/configuration/configure_audio.h index b8e347a42..9952739cf 100644 --- a/src/citra_qt/configuration/configure_audio.h +++ b/src/citra_qt/configuration/configure_audio.h @@ -23,7 +23,8 @@ public: void setConfiguration(); private: - void updateAudioDevices(int sink_index); + void updateAudioOutputDevices(int sink_index); + void updateAudioInputDevices(int index); void setOutputSinkFromSinkID(); void setAudioDeviceFromDeviceID(); diff --git a/src/citra_qt/configuration/configure_audio.ui b/src/citra_qt/configuration/configure_audio.ui index 9a3768b2a..d83fb0fcb 100644 --- a/src/citra_qt/configuration/configure_audio.ui +++ b/src/citra_qt/configuration/configure_audio.ui @@ -6,8 +6,8 @@ 0 0 - 188 - 246 + 329 + 332 @@ -39,7 +39,7 @@ - Output Engine: + Output Engine @@ -63,7 +63,7 @@ - Audio Device: + Audio Device @@ -137,6 +137,54 @@ + + + + Microphone + + + + + + + + Input Type + + + + + + + + None + + + + + Real Device + + + + + + + + + + + + Input Device + + + + + + + + + + + diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 6904aaaf6..b70ef57be 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -97,6 +97,8 @@ add_library(core STATIC frontend/framebuffer_layout.cpp frontend/framebuffer_layout.h frontend/input.h + frontend/mic.h + frontend/mic.cpp gdbstub/gdbstub.cpp gdbstub/gdbstub.h hle/applets/applet.cpp diff --git a/src/core/frontend/mic.cpp b/src/core/frontend/mic.cpp new file mode 100644 index 000000000..6ea843f41 --- /dev/null +++ b/src/core/frontend/mic.cpp @@ -0,0 +1,23 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/frontend/mic.h" +#include "core/hle/service/mic_u.h" + +namespace Frontend { + +static std::shared_ptr current_mic; + +void RegisterMic(std::shared_ptr mic) { + current_mic = mic; +} + +std::shared_ptr GetCurrentMic() { + if (!current_mic) { + current_mic = std::make_shared(); + } + return current_mic; +} + +} // namespace Frontend diff --git a/src/core/frontend/mic.h b/src/core/frontend/mic.h new file mode 100644 index 000000000..39ceae197 --- /dev/null +++ b/src/core/frontend/mic.h @@ -0,0 +1,110 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include "common/swap.h" + +namespace Frontend { + +namespace Mic { + +enum class Signedness : u8 { + Signed, + Unsigned, +}; + +struct Parameters { + Signedness sign; + u8 sample_size; + bool buffer_loop; + u32 sample_rate; + u32 buffer_offset; + u32 buffer_size; +}; + +class Interface { +public: + /// Starts the microphone. Called by Core + virtual void StartSampling(Parameters params) = 0; + + /// Stops the microphone. Called by Core + virtual void StopSampling() = 0; + + Interface() = default; + + Interface(const Interface& other) + : gain(other.gain), powered(other.powered), backing_memory(other.backing_memory), + backing_memory_size(other.backing_memory_size), parameters(other.parameters) {} + + /// Sets the backing memory that the mic should write raw samples into. Called by Core + void SetBackingMemory(u8* pointer, u32 size) { + backing_memory = pointer; + backing_memory_size = size; + } + + /// Adjusts the Parameters. Implementations should update the parameters field in addition to + /// changing the mic to sample according to the new parameters. Called by Core + virtual void AdjustSampleRate(u32 sample_rate) = 0; + + /// Value from 0 - 100 to adjust the mic gain setting. Called by Core + virtual void SetGain(u8 mic_gain) { + gain = mic_gain; + } + + u8 GetGain() const { + return gain; + } + + void SetPower(bool power) { + powered = power; + } + + bool GetPower() const { + return powered; + } + + bool IsSampling() const { + return is_sampling; + } + + Parameters GetParameters() const { + return parameters; + } + +protected: + u8* backing_memory; + u32 backing_memory_size; + + Parameters parameters; + u8 gain = 0; + bool is_sampling = false; + bool powered = false; +}; + +class NullMic final : public Interface { +public: + void StartSampling(Parameters params) override { + parameters = params; + is_sampling = true; + } + + void StopSampling() override { + is_sampling = false; + } + + void AdjustSampleRate(u32 sample_rate) override { + parameters.sample_rate = sample_rate; + } +}; + +} // namespace Mic + +void RegisterMic(std::shared_ptr mic); + +std::shared_ptr GetCurrentMic(); + +} // namespace Frontend diff --git a/src/core/hle/service/mic_u.cpp b/src/core/hle/service/mic_u.cpp index 6c97282aa..0014f5e8d 100644 --- a/src/core/hle/service/mic_u.cpp +++ b/src/core/hle/service/mic_u.cpp @@ -4,6 +4,7 @@ #include "common/logging/log.h" #include "core/core.h" +#include "core/frontend/mic.h" #include "core/hle/ipc.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/event.h" @@ -14,20 +15,37 @@ namespace Service::MIC { +/// Microphone audio encodings. enum class Encoding : u8 { - PCM8 = 0, - PCM16 = 1, - PCM8Signed = 2, - PCM16Signed = 3, + PCM8 = 0, ///< Unsigned 8-bit PCM. + PCM16 = 1, ///< Unsigned 16-bit PCM. + PCM8Signed = 2, ///< Signed 8-bit PCM. + PCM16Signed = 3, ///< Signed 16-bit PCM. }; +/// Microphone audio sampling rates. enum class SampleRate : u8 { - SampleRate32730 = 0, - SampleRate16360 = 1, - SampleRate10910 = 2, - SampleRate8180 = 3 + Rate32730 = 0, ///< 32728.498 Hz + Rate16360 = 1, ///< 16364.479 Hz + Rate10910 = 2, ///< 10909.499 Hz + Rate8180 = 3 ///< 8182.1245 Hz }; +constexpr u32 GetSampleRateInHz(SampleRate sample_rate) { + switch (sample_rate) { + case SampleRate::Rate8180: + return 8180; + case SampleRate::Rate10910: + return 10910; + case SampleRate::Rate16360: + return 16360; + case SampleRate::Rate32730: + return 32730; + default: + UNREACHABLE(); + } +} + struct MIC_U::Impl { explicit Impl(Core::System& system) { buffer_full_event = @@ -43,10 +61,12 @@ struct MIC_U::Impl { shared_memory->SetName("MIC_U:shared_memory"); } + mic->SetBackingMemory(shared_memory->GetPointer(), size); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_MIC, "called, size=0x{:X}", size); + LOG_TRACE(Service_MIC, "MIC:U MapSharedMem called, size=0x{:X}", size); } void UnmapSharedMem(Kernel::HLERequestContext& ctx) { @@ -54,55 +74,68 @@ struct MIC_U::Impl { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); shared_memory = nullptr; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_MIC, "called"); + LOG_TRACE(Service_MIC, "MIC:U UnmapSharedMem called"); } void StartSampling(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx, 0x03, 5, 0}; - encoding = rp.PopEnum(); - sample_rate = rp.PopEnum(); - audio_buffer_offset = rp.PopRaw(); - audio_buffer_size = rp.Pop(); - audio_buffer_loop = rp.Pop(); + Encoding encoding = rp.PopEnum(); + SampleRate sample_rate = rp.PopEnum(); + u32 audio_buffer_offset = rp.PopRaw(); + u32 audio_buffer_size = rp.Pop(); + bool audio_buffer_loop = rp.Pop(); + + auto sign = encoding == Encoding::PCM8Signed || encoding == Encoding::PCM16Signed + ? Frontend::Mic::Signedness::Signed + : Frontend::Mic::Signedness::Unsigned; + u8 sample_size = encoding == Encoding::PCM8Signed || encoding == Encoding::PCM8 ? 8 : 16; + + mic->StartSampling({sign, sample_size, audio_buffer_loop, GetSampleRateInHz(sample_rate), + audio_buffer_offset, audio_buffer_size}); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); - is_sampling = true; - LOG_WARNING(Service_MIC, - "(STUBBED) called, encoding={}, sample_rate={}, " - "audio_buffer_offset={}, audio_buffer_size={}, audio_buffer_loop={}", - static_cast(encoding), static_cast(sample_rate), audio_buffer_offset, - audio_buffer_size, audio_buffer_loop); + LOG_TRACE(Service_MIC, + "MIC:U StartSampling called, encoding={}, sample_rate={}, " + "audio_buffer_offset={}, audio_buffer_size={}, audio_buffer_loop={}", + static_cast(encoding), static_cast(sample_rate), audio_buffer_offset, + audio_buffer_size, audio_buffer_loop); } void AdjustSampling(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx, 0x04, 1, 0}; - sample_rate = rp.PopEnum(); + SampleRate sample_rate = rp.PopEnum(); + mic->AdjustSampleRate(GetSampleRateInHz(sample_rate)); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_MIC, "(STUBBED) called, sample_rate={}", static_cast(sample_rate)); + LOG_TRACE(Service_MIC, "MIC:U AdjustSampling sample_rate={}", + static_cast(sample_rate)); } void StopSampling(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx, 0x05, 0, 0}; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); - is_sampling = false; - LOG_WARNING(Service_MIC, "(STUBBED) called"); + mic->StopSampling(); + LOG_TRACE(Service_MIC, "MIC:U StopSampling called"); } void IsSampling(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx, 0x06, 0, 0}; + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(RESULT_SUCCESS); + bool is_sampling = mic->IsSampling(); rb.Push(is_sampling); - LOG_WARNING(Service_MIC, "(STUBBED) called"); + LOG_TRACE(Service_MIC, "MIC:U IsSampling: {}", is_sampling); } void GetBufferFullEvent(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx, 0x07, 0, 0}; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); rb.Push(RESULT_SUCCESS); rb.PushCopyObjects(buffer_full_event); @@ -111,11 +144,12 @@ struct MIC_U::Impl { void SetGain(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx, 0x08, 1, 0}; - mic_gain = rp.Pop(); + u8 gain = rp.Pop(); + mic->SetGain(gain); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_MIC, "(STUBBED) called, mic_gain={}", mic_gain); + LOG_TRACE(Service_MIC, "MIC:U SetGain gain={}", gain); } void GetGain(Kernel::HLERequestContext& ctx) { @@ -123,25 +157,29 @@ struct MIC_U::Impl { IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(RESULT_SUCCESS); - rb.Push(mic_gain); - LOG_WARNING(Service_MIC, "(STUBBED) called"); + u8 gain = mic->GetGain(); + rb.Push(gain); + LOG_TRACE(Service_MIC, "MIC:U GetGain gain={}", gain); } void SetPower(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx, 0x0A, 1, 0}; - mic_power = rp.Pop(); + bool power = rp.Pop(); + mic->SetPower(power); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_MIC, "(STUBBED) called, mic_power={}", mic_power); + LOG_TRACE(Service_MIC, "MIC:U SetPower mic_power={}", power); } void GetPower(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx, 0x0B, 0, 0}; + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(RESULT_SUCCESS); + bool mic_power = mic->GetPower(); rb.Push(mic_power); - LOG_WARNING(Service_MIC, "(STUBBED) called"); + LOG_TRACE(Service_MIC, "MIC:U GetPower called"); } void SetIirFilterMic(Kernel::HLERequestContext& ctx) { @@ -167,6 +205,7 @@ struct MIC_U::Impl { void GetClamp(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx, 0x0E, 0, 0}; + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(RESULT_SUCCESS); rb.Push(clamp); @@ -184,27 +223,20 @@ struct MIC_U::Impl { void SetClientVersion(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx, 0x10, 1, 0}; - const u32 version = rp.Pop(); LOG_WARNING(Service_MIC, "(STUBBED) called, version: 0x{:08X}", version); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); } - - u32 client_version = 0; - Kernel::SharedPtr buffer_full_event; + Kernel::SharedPtr buffer_full_event = + Core::System::GetInstance().Kernel().CreateEvent(Kernel::ResetType::OneShot, + "MIC_U::buffer_full_event"); Kernel::SharedPtr shared_memory; - u8 mic_gain = 0; - bool mic_power = false; - bool is_sampling = false; - bool allow_shell_closed; + u32 client_version = 0; + bool allow_shell_closed = false; bool clamp = false; - Encoding encoding = Encoding::PCM8; - SampleRate sample_rate = SampleRate::SampleRate32730; - s32 audio_buffer_offset = 0; - u32 audio_buffer_size = 0; - bool audio_buffer_loop = false; + std::shared_ptr mic; }; void MIC_U::MapSharedMem(Kernel::HLERequestContext& ctx) { @@ -292,10 +324,13 @@ MIC_U::MIC_U(Core::System& system) {0x00100040, &MIC_U::SetClientVersion, "SetClientVersion"}, }; + impl->mic = Frontend::GetCurrentMic(); RegisterHandlers(functions); } -MIC_U::~MIC_U() = default; +MIC_U::~MIC_U() { + impl->mic->StopSampling(); +} void InstallInterfaces(Core::System& system) { auto& service_manager = system.ServiceManager(); diff --git a/src/core/hle/service/mic_u.h b/src/core/hle/service/mic_u.h index bc4933229..bdd94fdd9 100644 --- a/src/core/hle/service/mic_u.h +++ b/src/core/hle/service/mic_u.h @@ -192,4 +192,6 @@ private: void InstallInterfaces(Core::System& system); +void ChangeMicImpl(); + } // namespace Service::MIC diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 4b80e3436..202a6c444 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -3,8 +3,11 @@ // Refer to the license.txt file included. #include +#include "audio_core/cubeb_input.h" #include "audio_core/dsp_interface.h" #include "core/core.h" +#include "core/frontend/emu_window.h" +#include "core/frontend/mic.h" #include "core/gdbstub/gdbstub.h" #include "core/hle/service/hid/hid.h" #include "core/hle/service/ir/ir_rst.h" @@ -57,6 +60,15 @@ void Apply() { cam->ReloadCameraDevices(); } } + // TODO support mic hotswapping by creating the new impl, and copying any parameters to it. + switch (Settings::values.mic_input_type) { + case 0: + Frontend::RegisterMic(std::make_shared()); + break; + case 1: + Frontend::RegisterMic(std::make_shared()); + break; + } } template diff --git a/src/core/settings.h b/src/core/settings.h index cea502693..702dd4d21 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -167,6 +167,8 @@ struct Values { bool enable_audio_stretching; std::string audio_device_id; float volume; + u8 mic_input_type; + std::string mic_input_device; // Camera std::array camera_name; From 5c9e327ff3f343a98de13034afdb99f1a419fac9 Mon Sep 17 00:00:00 2001 From: fearlessTobi Date: Fri, 1 Mar 2019 23:08:39 +0100 Subject: [PATCH 02/11] Fix --- src/audio_core/cubeb_input.cpp | 49 +++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/src/audio_core/cubeb_input.cpp b/src/audio_core/cubeb_input.cpp index 691c2b9c4..af023181b 100644 --- a/src/audio_core/cubeb_input.cpp +++ b/src/audio_core/cubeb_input.cpp @@ -23,6 +23,8 @@ struct CubebInput::Impl { u32 offset; u32 audio_buffer_size; + void UpdateOffset(int new_offset, Impl* impl); + void ProcessSample(const u8* data, int current_sample_index, Impl* impl); static long DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer, void* output_buffer, long num_frames); static void StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state); @@ -97,6 +99,25 @@ void CubebInput::AdjustSampleRate(u32 sample_rate) { LOG_ERROR(Audio, "AdjustSampleRate unimplemented!"); } +void CubebInput::Impl::UpdateOffset(int new_offset, Impl* impl) { + impl->offset = new_offset; + std::memcpy(impl->buffer + impl->audio_buffer_size, reinterpret_cast(&impl->offset), + sizeof(u32)); +} + +void CubebInput::Impl::ProcessSample(const u8* data, int current_sample_index, Impl* impl) { + if (impl->offset >= impl->audio_buffer_size) { + if (impl->looped_buffer) + UpdateOffset(impl->initial_offset, impl); + else + return; + } + + std::memcpy(impl->buffer + impl->offset, data + current_sample_index * sizeof(u16), + sizeof(u16)); + UpdateOffset(impl->offset + sizeof(u16), impl); +} + long CubebInput::Impl::DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer, void* output_buffer, long num_frames) { Impl* impl = static_cast(user_data); @@ -110,31 +131,15 @@ long CubebInput::Impl::DataCallback(cubeb_stream* stream, void* user_data, const return 0; } - u64 total_written = 0; - u64 to_write = num_frames; - u64 remaining_space = impl->audio_buffer_size - impl->offset; - if (to_write > remaining_space) { - to_write = remaining_space; + for (int i = 0; i < num_frames; i++) { + impl->ProcessSample(data, i, impl); } - std::memcpy(impl->buffer + impl->offset, data, to_write); - impl->offset += to_write; - total_written += to_write; - - if (impl->looped_buffer && num_frames > total_written) { - impl->offset = impl->initial_offset; - to_write = num_frames - to_write; - std::memcpy(impl->buffer + impl->offset, data, to_write); - impl->offset += to_write; - total_written += to_write; - } - // The last 4 bytes of the shared memory contains the latest offset - // so update that as well https://www.3dbrew.org/wiki/MIC_Shared_Memory - std::memcpy(impl->buffer + (impl->buffer_size - sizeof(u32)), - reinterpret_cast(&impl->offset), sizeof(u32)); // returning less than num_frames here signals cubeb to stop sampling - return total_written; -} + // return total_written; + // TODO:: Correct this + return num_frames; +} // namespace AudioCore void CubebInput::Impl::StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state) {} From c669aa8d55a7dbb0b1f9af0e92b062b1d7d012ab Mon Sep 17 00:00:00 2001 From: James Rowe Date: Mon, 4 Mar 2019 22:47:24 -0700 Subject: [PATCH 03/11] Rest of the owl --- src/audio_core/cubeb_input.cpp | 60 +++------- src/audio_core/cubeb_input.h | 4 +- src/citra_qt/configuration/configure_audio.ui | 5 + src/core/frontend/mic.cpp | 67 ++++++++++- src/core/frontend/mic.h | 56 +++++---- src/core/hle/service/mic_u.cpp | 113 ++++++++++++++++-- src/core/settings.cpp | 7 +- 7 files changed, 237 insertions(+), 75 deletions(-) diff --git a/src/audio_core/cubeb_input.cpp b/src/audio_core/cubeb_input.cpp index af023181b..6c7f3e740 100644 --- a/src/audio_core/cubeb_input.cpp +++ b/src/audio_core/cubeb_input.cpp @@ -10,21 +10,12 @@ namespace AudioCore { struct CubebInput::Impl { - // unsigned int sample_rate = 0; - // std::vector device_list; - cubeb* ctx = nullptr; cubeb_stream* stream = nullptr; - bool looped_buffer; - u8* buffer; - u32 buffer_size; - u32 initial_offset; - u32 offset; - u32 audio_buffer_size; + std::unique_ptr sample_queue{}; + u8 sample_size_in_bytes = 0; - void UpdateOffset(int new_offset, Impl* impl); - void ProcessSample(const u8* data, int current_sample_index, Impl* impl); static long DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer, void* output_buffer, long num_frames); static void StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state); @@ -35,6 +26,7 @@ CubebInput::CubebInput() : impl(std::make_unique()) { LOG_ERROR(Audio, "cubeb_init failed! Mic will not work properly"); return; } + impl->sample_queue = std::make_unique(); } CubebInput::~CubebInput() { @@ -60,11 +52,10 @@ void CubebInput::StartSampling(Frontend::Mic::Parameters params) { "Application requested unsupported 8 bit pcm format. Falling back to 16 bits"); } - impl->buffer = backing_memory; - impl->buffer_size = backing_memory_size; - impl->audio_buffer_size = params.buffer_size; - impl->offset = params.buffer_offset; - impl->looped_buffer = params.buffer_loop; + parameters = params; + is_sampling = true; + + impl->sample_size_in_bytes = 2; cubeb_devid input_device = nullptr; cubeb_stream_params input_params; @@ -92,6 +83,7 @@ void CubebInput::StopSampling() { if (impl->stream) { cubeb_stream_stop(impl->stream); } + is_sampling = false; } void CubebInput::AdjustSampleRate(u32 sample_rate) { @@ -99,47 +91,29 @@ void CubebInput::AdjustSampleRate(u32 sample_rate) { LOG_ERROR(Audio, "AdjustSampleRate unimplemented!"); } -void CubebInput::Impl::UpdateOffset(int new_offset, Impl* impl) { - impl->offset = new_offset; - std::memcpy(impl->buffer + impl->audio_buffer_size, reinterpret_cast(&impl->offset), - sizeof(u32)); -} - -void CubebInput::Impl::ProcessSample(const u8* data, int current_sample_index, Impl* impl) { - if (impl->offset >= impl->audio_buffer_size) { - if (impl->looped_buffer) - UpdateOffset(impl->initial_offset, impl); - else - return; +Frontend::Mic::Samples CubebInput::Read() { + Frontend::Mic::Samples samples{}; + Frontend::Mic::Samples queue; + while (impl->sample_queue->Pop(queue)) { + samples.insert(samples.end(), queue.begin(), queue.end()); } - - std::memcpy(impl->buffer + impl->offset, data + current_sample_index * sizeof(u16), - sizeof(u16)); - UpdateOffset(impl->offset + sizeof(u16), impl); + return samples; } long CubebInput::Impl::DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer, void* output_buffer, long num_frames) { Impl* impl = static_cast(user_data); - const u8* data = reinterpret_cast(input_buffer); + u8 const* data = reinterpret_cast(input_buffer); if (!impl) { return 0; } - if (!impl->buffer) { - return 0; - } - - for (int i = 0; i < num_frames; i++) { - impl->ProcessSample(data, i, impl); - } + impl->sample_queue->Push(std::vector(data, data + num_frames * impl->sample_size_in_bytes)); // returning less than num_frames here signals cubeb to stop sampling - // return total_written; - // TODO:: Correct this return num_frames; -} // namespace AudioCore +} void CubebInput::Impl::StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state) {} diff --git a/src/audio_core/cubeb_input.h b/src/audio_core/cubeb_input.h index be764ba43..20f9c4cea 100644 --- a/src/audio_core/cubeb_input.h +++ b/src/audio_core/cubeb_input.h @@ -13,7 +13,7 @@ namespace AudioCore { class CubebInput final : public Frontend::Mic::Interface { public: CubebInput(); - ~CubebInput(); + ~CubebInput() override; void StartSampling(Frontend::Mic::Parameters params) override; @@ -21,6 +21,8 @@ public: void AdjustSampleRate(u32 sample_rate) override; + Frontend::Mic::Samples Read() override; + private: struct Impl; std::unique_ptr impl; diff --git a/src/citra_qt/configuration/configure_audio.ui b/src/citra_qt/configuration/configure_audio.ui index d83fb0fcb..dd2f49173 100644 --- a/src/citra_qt/configuration/configure_audio.ui +++ b/src/citra_qt/configuration/configure_audio.ui @@ -164,6 +164,11 @@ Real Device + + + Static Noise + + diff --git a/src/core/frontend/mic.cpp b/src/core/frontend/mic.cpp index 6ea843f41..5d880fcf7 100644 --- a/src/core/frontend/mic.cpp +++ b/src/core/frontend/mic.cpp @@ -2,10 +2,73 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include #include "core/frontend/mic.h" #include "core/hle/service/mic_u.h" -namespace Frontend { +namespace Frontend::Mic { + +constexpr std::array NOISE_SAMPLE_8_BIT = { + 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF5, 0xFF, 0xFF, 0xFF, 0xFF, 0x8E, 0xFF, + 0xF4, 0xE1, 0xBF, 0x9A, 0x71, 0x58, 0x5B, 0x5F, 0x62, 0xC2, 0x25, 0x05, 0x01, 0x01, 0x01, 0x01}; + +constexpr std::array NOISE_SAMPLE_16_BIT = { + 0x64, 0x61, 0x74, 0x61, 0x56, 0xD7, 0x00, 0x00, 0x48, 0xF7, 0x86, 0x05, 0x77, 0x1A, 0xF4, 0x1F, + 0x28, 0x0F, 0x6B, 0xEB, 0x1C, 0xC0, 0xCB, 0x9D, 0x46, 0x90, 0xDF, 0x98, 0xEA, 0xAE, 0xB5, 0xC4, + 0x9D, 0xCE, 0xB6, 0xC9, 0xDF, 0xBD, 0x82, 0xBA, 0x83, 0xCD, 0x57, 0xF9, 0x96, 0x30, 0x2C, 0x5B, + 0x29, 0x64, 0xD3, 0x46, 0x0D, 0x12, 0x3E, 0xDE, 0x00, 0xBD, 0x74, 0xAE, 0xF0, 0xA4, 0x91, 0x93, + 0x02, 0x80, 0x00, 0x80, 0x03, 0x80, 0x6F, 0xAA, 0x8E, 0xE8, 0xEE, 0x1A, 0x2B, 0x2C, 0xC6, 0x18, + 0xF8, 0xED, 0xC2, 0xBE, 0x00, 0x99, 0xC1, 0x82, 0x00, 0x80, 0xA6, 0x8A, 0x37, 0xA8, 0x6B, 0xCE, + 0x20, 0xEF, 0xD0, 0xFD, 0x68, 0xF9, 0x50, 0xEF, 0x89, 0xF2, 0xDE, 0x0C, 0x24, 0x36, 0xDB, 0x58, + 0xB2, 0x61, 0xD5, 0x4C, 0x0B, 0x27, 0x6D, 0x02, 0xDC, 0xE8, 0x13, 0xD8, 0x2F, 0xC9, 0x07, 0xBC, + 0xAB, 0xBA, 0x70, 0xD1, 0xDE, 0x01, 0xF2, 0x3C, 0x64, 0x6A, 0xFF, 0x78, 0x47, 0x6B, 0x26, 0x56, + 0x9B, 0x51, 0xF3, 0x65, 0xFF, 0x7F, 0xFF, 0x7F, 0x88, 0x6B, 0x46, 0x24, 0xBF, 0xD8, 0x8C, 0xB4, + 0xD9, 0xCF, 0x77, 0x1C, 0x43, 0x6B, 0xFE, 0x7F, 0x15, 0x64, 0xA6, 0x13, 0x03, 0xCE, 0x51, 0xBF, + 0xC7, 0xEB, 0xCB, 0x2E, 0x6C, 0x58, 0xA7, 0x51, 0x40, 0x2A, 0x24, 0x06, 0x45, 0xFB, 0xD3, 0xFE, + 0x51, 0xF2, 0x1E, 0xC5, 0x79, 0x8A, 0x00, 0x80, 0x94, 0x8E, 0x7A, 0xDE, 0x83, 0x29, 0x8E, 0x3C, + 0x4B, 0x0F, 0xD8, 0xCB, 0x41, 0xAB, 0xC1, 0xC5, 0xC6, 0xFE, 0x0F, 0x1F, 0x92, 0x05, 0x4B, 0xC4, + 0xDC, 0x8F, 0xE1, 0x90, 0xE3, 0xC0, 0xDB, 0xF1, 0xCC, 0xF8, 0xC8, 0xD4, 0x2A, 0xAF, 0xB7, 0xB5, + 0x7F, 0xF0, 0xB2, 0x39, 0xD4, 0x5C, 0xD6, 0x41, 0x69, 0xFD, 0xEA, 0xBB, 0x57, 0x9B, 0x59, 0x98, + 0x8E, 0x9B, 0x8A, 0x97, 0x54, 0x98, 0xE6, 0xB5, 0x25, 0xF8, 0x77, 0x48, 0xFF, 0x7F, 0xFF, 0x7F, + 0x49, 0x6B, 0xB4, 0x4B, 0xE9, 0x45, 0xE3, 0x57, 0x60, 0x64, 0x8C, 0x4D, 0x69, 0x10, 0x10, 0xCA, + 0x13, 0xA4, 0xC5, 0xB7, 0xE4, 0xFD, 0x7C, 0x54, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xE0, 0x52, + 0x5E, 0x22, 0x31, 0x0B, 0xB5, 0x12, 0xB1, 0x2E, 0xA7, 0x4D, 0x17, 0x61, 0x46, 0x65, 0xA5, 0x5F, + 0x5C, 0x57, 0xB6, 0x4C, 0x3B, 0x38, 0xFD, 0x11, 0x70, 0xDC, 0x2A, 0xA8, 0xB9, 0x8C, 0xEA, 0x98, + 0x07, 0xC6, 0xDF, 0xF7, 0x2D, 0x0C, 0xEC, 0xF0, 0xA8, 0xB2, 0x02, 0x80, 0x00, 0x80, 0xBA, 0x8A, + 0x0E, 0xCB, 0x54, 0xF7, 0x2B, 0xEE, 0x4C, 0xB9, 0x48, 0x89, 0x8F, 0x90, 0x0E, 0xD8, 0x83, 0x32, + 0xC5, 0x5C, 0x54, 0x34, 0x86, 0xD6, 0x78, 0x8C, 0x88, 0x90, 0x87, 0xDE, 0x75, 0x37, 0x55, 0x56, + 0xF1, 0x28, 0xF0, 0xDA, 0xE2, 0xAB, 0xE6, 0xB6, 0xD6, 0xDF, 0x4D, 0xF4, 0xCF, 0xDE, 0x3E, 0xBA, + 0xC6, 0xB3, 0x81, 0xDA, 0xFE, 0x0D, 0xE9, 0x1D, 0xCE, 0xFB, 0x59, 0xCD, 0x57, 0xCA, 0x77, 0x06, + 0x63, 0x5A, 0xFE, 0x7F, 0x49, 0x63, 0x7F, 0x14, 0x0E, 0xDB, 0x2A, 0xE5, 0x3B, 0x27, 0xFF, 0x69, + 0x0A, 0x7C, 0xC5, 0x56, 0x65, 0x19, 0xEC, 0xE5, 0x0E, 0xC6, 0xA6, 0xB0, 0x09, 0xA2, 0x06, 0xA8, + 0xC7, 0xD1, 0xE9, 0x15, 0xD5, 0x4E, 0xBB, 0x56, 0x85, 0x2A, 0x25, 0xF0, 0x78, 0xD6, 0x70, 0xEB, + 0xF2, 0x0F, 0xE1, 0x15, 0x66, 0xEC, 0xC7, 0xB0, 0xE7, 0x93, 0x9D, 0xAD, 0xD1, 0xE6, 0xD3, 0x0D, + 0xAC, 0x00, 0xB2, 0xC7, 0x5D, 0x8B, 0x00, 0x80, 0xA1, 0x88, 0x14, 0xBE, 0xDF, 0xFB, 0xCF, 0x32, + 0xD8, 0x5B, 0x0F, 0x6F, 0x6C, 0x62, 0xD9, 0x33, 0x76, 0xF3, 0xD6, 0xBF, 0x41, 0xB3, 0x5C, 0xD1}; + +StaticMic::StaticMic() + : CACHE_8_BIT{NOISE_SAMPLE_8_BIT.begin(), NOISE_SAMPLE_8_BIT.end()}, + CACHE_16_BIT{NOISE_SAMPLE_16_BIT.begin(), NOISE_SAMPLE_16_BIT.end()} {} + +StaticMic::~StaticMic() = default; + +void StaticMic::StartSampling(Parameters params) { + sample_rate = params.sample_rate; + sample_size = params.sample_size; + + parameters = params; + is_sampling = true; +} + +void StaticMic::StopSampling() { + is_sampling = false; +} + +void StaticMic::AdjustSampleRate(u32 sample_rate) {} + +Samples StaticMic::Read() { + return (sample_size == 8) ? CACHE_8_BIT : CACHE_16_BIT; +} static std::shared_ptr current_mic; @@ -20,4 +83,4 @@ std::shared_ptr GetCurrentMic() { return current_mic; } -} // namespace Frontend +} // namespace Frontend::Mic diff --git a/src/core/frontend/mic.h b/src/core/frontend/mic.h index 39ceae197..13f79e4fa 100644 --- a/src/core/frontend/mic.h +++ b/src/core/frontend/mic.h @@ -4,19 +4,21 @@ #pragma once -#include #include +#include #include "common/swap.h" +#include "common/threadsafe_queue.h" -namespace Frontend { - -namespace Mic { +namespace Frontend::Mic { enum class Signedness : u8 { Signed, Unsigned, }; +using Samples = std::vector; +using SampleQueue = Common::SPSCQueue; + struct Parameters { Signedness sign; u8 sample_size; @@ -28,23 +30,20 @@ struct Parameters { class Interface { public: + Interface() = default; + + virtual ~Interface() = default; + /// Starts the microphone. Called by Core virtual void StartSampling(Parameters params) = 0; /// Stops the microphone. Called by Core virtual void StopSampling() = 0; - Interface() = default; - - Interface(const Interface& other) - : gain(other.gain), powered(other.powered), backing_memory(other.backing_memory), - backing_memory_size(other.backing_memory_size), parameters(other.parameters) {} - - /// Sets the backing memory that the mic should write raw samples into. Called by Core - void SetBackingMemory(u8* pointer, u32 size) { - backing_memory = pointer; - backing_memory_size = size; - } + /// Called from the actual event timing read back. The frontend impl is responsible for wrapping + /// up any data and returning them to the core so the core can write them to the sharedmem. If + /// theres nothing to return just return an empty vector + virtual Samples Read() = 0; /// Adjusts the Parameters. Implementations should update the parameters field in addition to /// changing the mic to sample according to the new parameters. Called by Core @@ -76,9 +75,6 @@ public: } protected: - u8* backing_memory; - u32 backing_memory_size; - Parameters parameters; u8 gain = 0; bool is_sampling = false; @@ -99,12 +95,32 @@ public: void AdjustSampleRate(u32 sample_rate) override { parameters.sample_rate = sample_rate; } + + Samples Read() override { + return {}; + } }; -} // namespace Mic +class StaticMic final : public Interface { +public: + StaticMic(); + ~StaticMic() override; + + void StartSampling(Parameters params) override; + void StopSampling() override; + void AdjustSampleRate(u32 sample_rate) override; + + Samples Read() override; + +private: + u16 sample_rate = 0; + u8 sample_size = 0; + std::vector CACHE_8_BIT; + std::vector CACHE_16_BIT; +}; void RegisterMic(std::shared_ptr mic); std::shared_ptr GetCurrentMic(); -} // namespace Frontend +} // namespace Frontend::Mic diff --git a/src/core/hle/service/mic_u.cpp b/src/core/hle/service/mic_u.cpp index 0014f5e8d..577d7740f 100644 --- a/src/core/hle/service/mic_u.cpp +++ b/src/core/hle/service/mic_u.cpp @@ -46,10 +46,75 @@ constexpr u32 GetSampleRateInHz(SampleRate sample_rate) { } } +// The following buffer write rates were found by hardware test on o3ds and n3ds. +// The 3ds writes to the sharedmem roughly every 15 samples +constexpr u64 BufferUpdateRate8180 = BASE_CLOCK_RATE_ARM11 / 511; +constexpr u64 BufferUpdateRate10910 = BASE_CLOCK_RATE_ARM11 / 681; +constexpr u64 BufferUpdateRate16360 = BASE_CLOCK_RATE_ARM11 / 1022; +constexpr u64 BufferUpdateRate32730 = BASE_CLOCK_RATE_ARM11 / 2045; + +constexpr u64 GetBufferUpdateRate(SampleRate sample_rate) { + switch (sample_rate) { + case SampleRate::Rate8180: + return BufferUpdateRate8180; + case SampleRate::Rate10910: + return BufferUpdateRate10910; + case SampleRate::Rate16360: + return BufferUpdateRate16360; + case SampleRate::Rate32730: + return BufferUpdateRate32730; + default: + UNREACHABLE(); + } +} + +// Variables holding the current mic buffer writing state +struct State { + u8* sharedmem_buffer = nullptr; + u32 sharedmem_size = 0; + size_t size = 0; + u32 offset = 0; + u32 initial_offset = 0; + bool looped_buffer = false; + u8 sample_size = 0; + SampleRate sample_rate = SampleRate::Rate16360; + + void UpdateOffset() { + // The last 4 bytes of the shared memory contains the latest offset + // so update that as well https://www.3dbrew.org/wiki/MIC_Shared_Memory + std::memcpy(sharedmem_buffer + (sharedmem_size - sizeof(u32)), + reinterpret_cast(&offset), sizeof(u32)); + } + + void WriteSamples(const std::vector& samples) { + u32 bytes_total_written = 0; + const size_t remaining_space = size - offset; + size_t bytes_to_write = std::min(samples.size(), remaining_space); + + // Write as many samples as we can to the buffer. + // TODO if the sample size is 16bit, this could theoretically cut a sample + std::memcpy(sharedmem_buffer + offset, samples.data(), bytes_to_write); + offset += static_cast(bytes_to_write); + bytes_total_written += static_cast(bytes_to_write); + + // If theres any samples left to write after we looped, go ahead and write them now + if (looped_buffer && samples.size() > bytes_total_written) { + offset = initial_offset; + bytes_to_write = std::min(samples.size() - bytes_total_written, size); + std::memcpy(sharedmem_buffer + offset, samples.data() + bytes_total_written, + bytes_to_write); + offset += static_cast(bytes_to_write); + } + } +}; + struct MIC_U::Impl { - explicit Impl(Core::System& system) { + explicit Impl(Core::System& system) : timing(system.CoreTiming()) { buffer_full_event = system.Kernel().CreateEvent(Kernel::ResetType::OneShot, "MIC_U::buffer_full_event"); + buffer_write_event = timing.RegisterEvent( + "MIC_U::UpdateBuffer", std::bind(&Impl::UpdateSharedMemBuffer, this, + std::placeholders::_1, std::placeholders::_2)); } void MapSharedMem(Kernel::HLERequestContext& ctx) { @@ -59,10 +124,10 @@ struct MIC_U::Impl { if (shared_memory) { shared_memory->SetName("MIC_U:shared_memory"); + state.sharedmem_buffer = shared_memory->GetPointer(); + state.sharedmem_size = size; } - mic->SetBackingMemory(shared_memory->GetPointer(), size); - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); @@ -77,6 +142,25 @@ struct MIC_U::Impl { LOG_TRACE(Service_MIC, "MIC:U UnmapSharedMem called"); } + void UpdateSharedMemBuffer(u64 userdata, s64 cycles_late) { + // If the event was scheduled before the application requested the mic to stop sampling + if (!mic->IsSampling()) { + return; + } + + Frontend::Mic::Samples samples = mic->Read(); + if (!samples.empty()) { + // write the samples to sharedmem page + state.WriteSamples(samples); + // write the new offset to the last 4 bytes of the buffer + state.UpdateOffset(); + } + + // schedule next run + timing.ScheduleEvent(GetBufferUpdateRate(state.sample_rate) - cycles_late, + buffer_write_event); + } + void StartSampling(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx, 0x03, 5, 0}; @@ -86,14 +170,27 @@ struct MIC_U::Impl { u32 audio_buffer_size = rp.Pop(); bool audio_buffer_loop = rp.Pop(); + if (mic->IsSampling()) { + LOG_CRITICAL(Service_MIC, + "Application started sampling again before stopping sampling"); + mic->StopSampling(); + } + auto sign = encoding == Encoding::PCM8Signed || encoding == Encoding::PCM16Signed ? Frontend::Mic::Signedness::Signed : Frontend::Mic::Signedness::Unsigned; u8 sample_size = encoding == Encoding::PCM8Signed || encoding == Encoding::PCM8 ? 8 : 16; + state.offset = state.initial_offset = audio_buffer_offset; + state.sample_rate = sample_rate; + state.sample_size = sample_size; + state.looped_buffer = audio_buffer_loop; + state.size = audio_buffer_size; mic->StartSampling({sign, sample_size, audio_buffer_loop, GetSampleRateInHz(sample_rate), audio_buffer_offset, audio_buffer_size}); + timing.ScheduleEvent(GetBufferUpdateRate(state.sample_rate), buffer_write_event); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); LOG_TRACE(Service_MIC, @@ -229,14 +326,16 @@ struct MIC_U::Impl { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); } - Kernel::SharedPtr buffer_full_event = - Core::System::GetInstance().Kernel().CreateEvent(Kernel::ResetType::OneShot, - "MIC_U::buffer_full_event"); + + Kernel::SharedPtr buffer_full_event; + Core::TimingEventType* buffer_write_event = nullptr; Kernel::SharedPtr shared_memory; u32 client_version = 0; bool allow_shell_closed = false; bool clamp = false; std::shared_ptr mic; + Core::Timing& timing; + State state{}; }; void MIC_U::MapSharedMem(Kernel::HLERequestContext& ctx) { @@ -324,7 +423,7 @@ MIC_U::MIC_U(Core::System& system) {0x00100040, &MIC_U::SetClientVersion, "SetClientVersion"}, }; - impl->mic = Frontend::GetCurrentMic(); + impl->mic = Frontend::Mic::GetCurrentMic(); RegisterHandlers(functions); } diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 202a6c444..cd810f2db 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -63,10 +63,13 @@ void Apply() { // TODO support mic hotswapping by creating the new impl, and copying any parameters to it. switch (Settings::values.mic_input_type) { case 0: - Frontend::RegisterMic(std::make_shared()); + Frontend::Mic::RegisterMic(std::make_shared()); break; case 1: - Frontend::RegisterMic(std::make_shared()); + Frontend::Mic::RegisterMic(std::make_shared()); + break; + case 2: + Frontend::Mic::RegisterMic(std::make_shared()); break; } } From 5f532c25600e0ed4d36632d06093ed07fb80fea6 Mon Sep 17 00:00:00 2001 From: James Rowe Date: Wed, 6 Mar 2019 11:16:43 -0700 Subject: [PATCH 04/11] Address review comments --- src/audio_core/cubeb_input.cpp | 13 +-- src/audio_core/cubeb_input.h | 2 +- src/citra/config.cpp | 4 + src/citra_qt/configuration/config.cpp | 7 +- .../configuration/configure_audio.cpp | 13 +-- src/core/frontend/mic.cpp | 70 +++++--------- src/core/frontend/mic.h | 36 +++---- src/core/hle/service/mic_u.cpp | 93 ++++++++++--------- src/core/hle/service/mic_u.h | 2 - src/core/settings.cpp | 6 +- src/core/settings.h | 8 +- 11 files changed, 119 insertions(+), 135 deletions(-) diff --git a/src/audio_core/cubeb_input.cpp b/src/audio_core/cubeb_input.cpp index 6c7f3e740..5666b09bb 100644 --- a/src/audio_core/cubeb_input.cpp +++ b/src/audio_core/cubeb_input.cpp @@ -9,11 +9,13 @@ namespace AudioCore { +using SampleQueue = Common::SPSCQueue; + struct CubebInput::Impl { cubeb* ctx = nullptr; cubeb_stream* stream = nullptr; - std::unique_ptr sample_queue{}; + std::unique_ptr sample_queue{}; u8 sample_size_in_bytes = 0; static long DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer, @@ -26,7 +28,7 @@ CubebInput::CubebInput() : impl(std::make_unique()) { LOG_ERROR(Audio, "cubeb_init failed! Mic will not work properly"); return; } - impl->sample_queue = std::make_unique(); + impl->sample_queue = std::make_unique(); } CubebInput::~CubebInput() { @@ -40,7 +42,7 @@ CubebInput::~CubebInput() { cubeb_destroy(impl->ctx); } -void CubebInput::StartSampling(Frontend::Mic::Parameters params) { +void CubebInput::StartSampling(const Frontend::Mic::Parameters& params) { // Cubeb apparently only supports signed 16 bit PCM (and float32 which the 3ds doesn't support) // TODO resample the input stream if (params.sign == Frontend::Mic::Signedness::Unsigned) { @@ -103,12 +105,11 @@ Frontend::Mic::Samples CubebInput::Read() { long CubebInput::Impl::DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer, void* output_buffer, long num_frames) { Impl* impl = static_cast(user_data); - u8 const* data = reinterpret_cast(input_buffer); - if (!impl) { return 0; } + u8 const* data = reinterpret_cast(input_buffer); impl->sample_queue->Push(std::vector(data, data + num_frames * impl->sample_size_in_bytes)); // returning less than num_frames here signals cubeb to stop sampling @@ -130,7 +131,7 @@ std::vector ListCubebInputDevices() { if (cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_INPUT, &collection) != CUBEB_OK) { LOG_WARNING(Audio_Sink, "Audio input device enumeration not supported"); } else { - for (size_t i = 0; i < collection.count; i++) { + for (std::size_t i = 0; i < collection.count; i++) { const cubeb_device_info& device = collection.device[i]; if (device.state == CUBEB_DEVICE_STATE_ENABLED && device.friendly_name) { device_list.emplace_back(device.friendly_name); diff --git a/src/audio_core/cubeb_input.h b/src/audio_core/cubeb_input.h index 20f9c4cea..a38737849 100644 --- a/src/audio_core/cubeb_input.h +++ b/src/audio_core/cubeb_input.h @@ -15,7 +15,7 @@ public: CubebInput(); ~CubebInput() override; - void StartSampling(Frontend::Mic::Parameters params) override; + void StartSampling(const Frontend::Mic::Parameters& params) override; void StopSampling() override; diff --git a/src/citra/config.cpp b/src/citra/config.cpp index e6eea37cc..70f05b7b5 100644 --- a/src/citra/config.cpp +++ b/src/citra/config.cpp @@ -166,6 +166,10 @@ void Config::ReadValues() { sdl2_config->GetBoolean("Audio", "enable_audio_stretching", true); Settings::values.audio_device_id = sdl2_config->GetString("Audio", "output_device", "auto"); Settings::values.volume = sdl2_config->GetReal("Audio", "volume", 1); + Settings::values.mic_input_device = + sdl2_config->GetString("Audio", "mic_input_device", "Default"); + Settings::values.mic_input_type = + static_cast(sdl2_config->GetInteger("Audio", "mic_input_type", 0)); // Data Storage Settings::values.use_virtual_sd = diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index 29f61fae3..2f55a4e92 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -198,7 +198,8 @@ void Config::ReadValues() { Settings::values.audio_device_id = ReadSetting("output_device", "auto").toString().toStdString(); Settings::values.volume = ReadSetting("volume", 1).toFloat(); - Settings::values.mic_input_type = ReadSetting("mic_input_type", 0).toInt(); + Settings::values.mic_input_type = + static_cast(ReadSetting("mic_input_type", 0).toInt()); Settings::values.mic_input_device = ReadSetting("mic_input_device", "Default").toString().toStdString(); qt_config->endGroup(); @@ -483,8 +484,8 @@ void Config::SaveValues() { WriteSetting("enable_audio_stretching", Settings::values.enable_audio_stretching, true); WriteSetting("output_device", QString::fromStdString(Settings::values.audio_device_id), "auto"); WriteSetting("volume", Settings::values.volume, 1.0f); - WriteSetting("mic_input_device", QString::fromStdString(Settings::values.mic_input_device), 0); - WriteSetting("mic_input_type", Settings::values.mic_input_type, "Default"); + WriteSetting("mic_input_device", QString::fromStdString(Settings::values.mic_input_device), "Default"); + WriteSetting("mic_input_type", static_cast(Settings::values.mic_input_type), 0); qt_config->endGroup(); using namespace Service::CAM; diff --git a/src/citra_qt/configuration/configure_audio.cpp b/src/citra_qt/configuration/configure_audio.cpp index 1d9731cd3..e84c2f14d 100644 --- a/src/citra_qt/configuration/configure_audio.cpp +++ b/src/citra_qt/configuration/configure_audio.cpp @@ -3,7 +3,6 @@ // Refer to the license.txt file included. #include -#include #include #include "audio_core/cubeb_input.h" #include "audio_core/sink.h" @@ -37,14 +36,14 @@ ConfigureAudio::ConfigureAudio(QWidget* parent) ui->input_device_combo_box->addItem(QString::fromStdString(device)); } - connect(ui->input_type_combo_box, QOverload::of(&QComboBox::currentIndexChanged), this, + connect(ui->input_type_combo_box, qOverload(&QComboBox::currentIndexChanged), this, &ConfigureAudio::updateAudioInputDevices); ui->input_type_combo_box->setEnabled(!Core::System::GetInstance().IsPoweredOn()); ui->input_device_combo_box->setEnabled(!Core::System::GetInstance().IsPoweredOn()); this->setConfiguration(); - connect(ui->output_sink_combo_box, QOverload::of(&QComboBox::currentIndexChanged), this, + connect(ui->output_sink_combo_box, qOverload(&QComboBox::currentIndexChanged), this, &ConfigureAudio::updateAudioOutputDevices); } @@ -74,10 +73,11 @@ void ConfigureAudio::setConfiguration() { } ui->emulation_combo_box->setCurrentIndex(selection); - ui->input_type_combo_box->setCurrentIndex(Settings::values.mic_input_type); + int index = static_cast(Settings::values.mic_input_type); + ui->input_type_combo_box->setCurrentIndex(index); ui->input_device_combo_box->setCurrentText( QString::fromStdString(Settings::values.mic_input_device)); - updateAudioInputDevices(Settings::values.mic_input_type); + updateAudioInputDevices(index); } void ConfigureAudio::setOutputSinkFromSinkID() { @@ -124,7 +124,8 @@ void ConfigureAudio::applyConfiguration() { static_cast(ui->volume_slider->sliderPosition()) / ui->volume_slider->maximum(); Settings::values.enable_dsp_lle = ui->emulation_combo_box->currentIndex() != 0; Settings::values.enable_dsp_lle_multithread = ui->emulation_combo_box->currentIndex() == 2; - Settings::values.mic_input_type = ui->input_type_combo_box->currentIndex(); + Settings::values.mic_input_type = + static_cast(ui->input_type_combo_box->currentIndex()); Settings::values.mic_input_device = ui->input_device_combo_box->currentText().toStdString(); } diff --git a/src/core/frontend/mic.cpp b/src/core/frontend/mic.cpp index 5d880fcf7..84f090cb2 100644 --- a/src/core/frontend/mic.cpp +++ b/src/core/frontend/mic.cpp @@ -1,4 +1,4 @@ -// Copyright 2018 Citra Emulator Project +// Copyright 2019 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -12,39 +12,28 @@ constexpr std::array NOISE_SAMPLE_8_BIT = { 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF5, 0xFF, 0xFF, 0xFF, 0xFF, 0x8E, 0xFF, 0xF4, 0xE1, 0xBF, 0x9A, 0x71, 0x58, 0x5B, 0x5F, 0x62, 0xC2, 0x25, 0x05, 0x01, 0x01, 0x01, 0x01}; -constexpr std::array NOISE_SAMPLE_16_BIT = { +constexpr std::array NOISE_SAMPLE_16_BIT = { 0x64, 0x61, 0x74, 0x61, 0x56, 0xD7, 0x00, 0x00, 0x48, 0xF7, 0x86, 0x05, 0x77, 0x1A, 0xF4, 0x1F, - 0x28, 0x0F, 0x6B, 0xEB, 0x1C, 0xC0, 0xCB, 0x9D, 0x46, 0x90, 0xDF, 0x98, 0xEA, 0xAE, 0xB5, 0xC4, - 0x9D, 0xCE, 0xB6, 0xC9, 0xDF, 0xBD, 0x82, 0xBA, 0x83, 0xCD, 0x57, 0xF9, 0x96, 0x30, 0x2C, 0x5B, - 0x29, 0x64, 0xD3, 0x46, 0x0D, 0x12, 0x3E, 0xDE, 0x00, 0xBD, 0x74, 0xAE, 0xF0, 0xA4, 0x91, 0x93, - 0x02, 0x80, 0x00, 0x80, 0x03, 0x80, 0x6F, 0xAA, 0x8E, 0xE8, 0xEE, 0x1A, 0x2B, 0x2C, 0xC6, 0x18, - 0xF8, 0xED, 0xC2, 0xBE, 0x00, 0x99, 0xC1, 0x82, 0x00, 0x80, 0xA6, 0x8A, 0x37, 0xA8, 0x6B, 0xCE, - 0x20, 0xEF, 0xD0, 0xFD, 0x68, 0xF9, 0x50, 0xEF, 0x89, 0xF2, 0xDE, 0x0C, 0x24, 0x36, 0xDB, 0x58, - 0xB2, 0x61, 0xD5, 0x4C, 0x0B, 0x27, 0x6D, 0x02, 0xDC, 0xE8, 0x13, 0xD8, 0x2F, 0xC9, 0x07, 0xBC, - 0xAB, 0xBA, 0x70, 0xD1, 0xDE, 0x01, 0xF2, 0x3C, 0x64, 0x6A, 0xFF, 0x78, 0x47, 0x6B, 0x26, 0x56, - 0x9B, 0x51, 0xF3, 0x65, 0xFF, 0x7F, 0xFF, 0x7F, 0x88, 0x6B, 0x46, 0x24, 0xBF, 0xD8, 0x8C, 0xB4, - 0xD9, 0xCF, 0x77, 0x1C, 0x43, 0x6B, 0xFE, 0x7F, 0x15, 0x64, 0xA6, 0x13, 0x03, 0xCE, 0x51, 0xBF, - 0xC7, 0xEB, 0xCB, 0x2E, 0x6C, 0x58, 0xA7, 0x51, 0x40, 0x2A, 0x24, 0x06, 0x45, 0xFB, 0xD3, 0xFE, - 0x51, 0xF2, 0x1E, 0xC5, 0x79, 0x8A, 0x00, 0x80, 0x94, 0x8E, 0x7A, 0xDE, 0x83, 0x29, 0x8E, 0x3C, - 0x4B, 0x0F, 0xD8, 0xCB, 0x41, 0xAB, 0xC1, 0xC5, 0xC6, 0xFE, 0x0F, 0x1F, 0x92, 0x05, 0x4B, 0xC4, - 0xDC, 0x8F, 0xE1, 0x90, 0xE3, 0xC0, 0xDB, 0xF1, 0xCC, 0xF8, 0xC8, 0xD4, 0x2A, 0xAF, 0xB7, 0xB5, - 0x7F, 0xF0, 0xB2, 0x39, 0xD4, 0x5C, 0xD6, 0x41, 0x69, 0xFD, 0xEA, 0xBB, 0x57, 0x9B, 0x59, 0x98, - 0x8E, 0x9B, 0x8A, 0x97, 0x54, 0x98, 0xE6, 0xB5, 0x25, 0xF8, 0x77, 0x48, 0xFF, 0x7F, 0xFF, 0x7F, - 0x49, 0x6B, 0xB4, 0x4B, 0xE9, 0x45, 0xE3, 0x57, 0x60, 0x64, 0x8C, 0x4D, 0x69, 0x10, 0x10, 0xCA, - 0x13, 0xA4, 0xC5, 0xB7, 0xE4, 0xFD, 0x7C, 0x54, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xE0, 0x52, - 0x5E, 0x22, 0x31, 0x0B, 0xB5, 0x12, 0xB1, 0x2E, 0xA7, 0x4D, 0x17, 0x61, 0x46, 0x65, 0xA5, 0x5F, - 0x5C, 0x57, 0xB6, 0x4C, 0x3B, 0x38, 0xFD, 0x11, 0x70, 0xDC, 0x2A, 0xA8, 0xB9, 0x8C, 0xEA, 0x98, - 0x07, 0xC6, 0xDF, 0xF7, 0x2D, 0x0C, 0xEC, 0xF0, 0xA8, 0xB2, 0x02, 0x80, 0x00, 0x80, 0xBA, 0x8A, - 0x0E, 0xCB, 0x54, 0xF7, 0x2B, 0xEE, 0x4C, 0xB9, 0x48, 0x89, 0x8F, 0x90, 0x0E, 0xD8, 0x83, 0x32, - 0xC5, 0x5C, 0x54, 0x34, 0x86, 0xD6, 0x78, 0x8C, 0x88, 0x90, 0x87, 0xDE, 0x75, 0x37, 0x55, 0x56, - 0xF1, 0x28, 0xF0, 0xDA, 0xE2, 0xAB, 0xE6, 0xB6, 0xD6, 0xDF, 0x4D, 0xF4, 0xCF, 0xDE, 0x3E, 0xBA, - 0xC6, 0xB3, 0x81, 0xDA, 0xFE, 0x0D, 0xE9, 0x1D, 0xCE, 0xFB, 0x59, 0xCD, 0x57, 0xCA, 0x77, 0x06, - 0x63, 0x5A, 0xFE, 0x7F, 0x49, 0x63, 0x7F, 0x14, 0x0E, 0xDB, 0x2A, 0xE5, 0x3B, 0x27, 0xFF, 0x69, - 0x0A, 0x7C, 0xC5, 0x56, 0x65, 0x19, 0xEC, 0xE5, 0x0E, 0xC6, 0xA6, 0xB0, 0x09, 0xA2, 0x06, 0xA8, - 0xC7, 0xD1, 0xE9, 0x15, 0xD5, 0x4E, 0xBB, 0x56, 0x85, 0x2A, 0x25, 0xF0, 0x78, 0xD6, 0x70, 0xEB, - 0xF2, 0x0F, 0xE1, 0x15, 0x66, 0xEC, 0xC7, 0xB0, 0xE7, 0x93, 0x9D, 0xAD, 0xD1, 0xE6, 0xD3, 0x0D, - 0xAC, 0x00, 0xB2, 0xC7, 0x5D, 0x8B, 0x00, 0x80, 0xA1, 0x88, 0x14, 0xBE, 0xDF, 0xFB, 0xCF, 0x32, - 0xD8, 0x5B, 0x0F, 0x6F, 0x6C, 0x62, 0xD9, 0x33, 0x76, 0xF3, 0xD6, 0xBF, 0x41, 0xB3, 0x5C, 0xD1}; + 0x28, 0x0F, 0x6B, 0xEB, 0x1C, 0xC0, 0xCB, 0x9D, 0x46, 0x90, 0xDF, 0x98, 0xEA, 0xAE, 0xB5, 0xC4}; + +Interface::~Interface() = default; + +void NullMic::StartSampling(const Parameters& params) { + parameters = params; + is_sampling = true; +} + +void NullMic::StopSampling() { + is_sampling = false; +} + +void NullMic::AdjustSampleRate(u32 sample_rate) { + parameters.sample_rate = sample_rate; +} + +Samples NullMic::Read() { + return {}; +} StaticMic::StaticMic() : CACHE_8_BIT{NOISE_SAMPLE_8_BIT.begin(), NOISE_SAMPLE_8_BIT.end()}, @@ -52,7 +41,7 @@ StaticMic::StaticMic() StaticMic::~StaticMic() = default; -void StaticMic::StartSampling(Parameters params) { +void StaticMic::StartSampling(const Parameters& params) { sample_rate = params.sample_rate; sample_size = params.sample_size; @@ -70,17 +59,4 @@ Samples StaticMic::Read() { return (sample_size == 8) ? CACHE_8_BIT : CACHE_16_BIT; } -static std::shared_ptr current_mic; - -void RegisterMic(std::shared_ptr mic) { - current_mic = mic; -} - -std::shared_ptr GetCurrentMic() { - if (!current_mic) { - current_mic = std::make_shared(); - } - return current_mic; -} - } // namespace Frontend::Mic diff --git a/src/core/frontend/mic.h b/src/core/frontend/mic.h index 13f79e4fa..296e434f6 100644 --- a/src/core/frontend/mic.h +++ b/src/core/frontend/mic.h @@ -1,4 +1,4 @@ -// Copyright 2018 Citra Emulator Project +// Copyright 2019 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -17,7 +17,6 @@ enum class Signedness : u8 { }; using Samples = std::vector; -using SampleQueue = Common::SPSCQueue; struct Parameters { Signedness sign; @@ -32,17 +31,19 @@ class Interface { public: Interface() = default; - virtual ~Interface() = default; + virtual ~Interface(); /// Starts the microphone. Called by Core - virtual void StartSampling(Parameters params) = 0; + virtual void StartSampling(const Parameters& params) = 0; /// Stops the microphone. Called by Core virtual void StopSampling() = 0; - /// Called from the actual event timing read back. The frontend impl is responsible for wrapping - /// up any data and returning them to the core so the core can write them to the sharedmem. If - /// theres nothing to return just return an empty vector + /** + * Called from the actual event timing at a constant period under a given sample rate. + * When sampling is enabled this function is expected to return a buffer of 16 samples in ideal + * conditions, but can be lax if the data is coming in from another source like a real mic. + */ virtual Samples Read() = 0; /// Adjusts the Parameters. Implementations should update the parameters field in addition to @@ -70,7 +71,7 @@ public: return is_sampling; } - Parameters GetParameters() const { + const Parameters& GetParameters() const { return parameters; } @@ -83,22 +84,13 @@ protected: class NullMic final : public Interface { public: - void StartSampling(Parameters params) override { - parameters = params; - is_sampling = true; - } + void StartSampling(const Parameters& params) override; - void StopSampling() override { - is_sampling = false; - } + void StopSampling() override; - void AdjustSampleRate(u32 sample_rate) override { - parameters.sample_rate = sample_rate; - } + void AdjustSampleRate(u32 sample_rate) override; - Samples Read() override { - return {}; - } + Samples Read() override; }; class StaticMic final : public Interface { @@ -106,7 +98,7 @@ public: StaticMic(); ~StaticMic() override; - void StartSampling(Parameters params) override; + void StartSampling(const Parameters& params) override; void StopSampling() override; void AdjustSampleRate(u32 sample_rate) override; diff --git a/src/core/hle/service/mic_u.cpp b/src/core/hle/service/mic_u.cpp index 577d7740f..0ac6a1340 100644 --- a/src/core/hle/service/mic_u.cpp +++ b/src/core/hle/service/mic_u.cpp @@ -23,7 +23,8 @@ enum class Encoding : u8 { PCM16Signed = 3, ///< Signed 16-bit PCM. }; -/// Microphone audio sampling rates. +/// Microphone audio sampling rates. The actual accurate sampling rate can be calculated using +/// (16756991 / 512) / (SampleRate + 1) where SampleRate is one of the above values. enum class SampleRate : u8 { Rate32730 = 0, ///< 32728.498 Hz Rate16360 = 1, ///< 16364.479 Hz @@ -34,13 +35,13 @@ enum class SampleRate : u8 { constexpr u32 GetSampleRateInHz(SampleRate sample_rate) { switch (sample_rate) { case SampleRate::Rate8180: - return 8180; + return 8182; case SampleRate::Rate10910: - return 10910; + return 10909; case SampleRate::Rate16360: - return 16360; + return 16364; case SampleRate::Rate32730: - return 32730; + return 32728; default: UNREACHABLE(); } @@ -54,45 +55,28 @@ constexpr u64 BufferUpdateRate16360 = BASE_CLOCK_RATE_ARM11 / 1022; constexpr u64 BufferUpdateRate32730 = BASE_CLOCK_RATE_ARM11 / 2045; constexpr u64 GetBufferUpdateRate(SampleRate sample_rate) { - switch (sample_rate) { - case SampleRate::Rate8180: - return BufferUpdateRate8180; - case SampleRate::Rate10910: - return BufferUpdateRate10910; - case SampleRate::Rate16360: - return BufferUpdateRate16360; - case SampleRate::Rate32730: - return BufferUpdateRate32730; - default: - UNREACHABLE(); - } + return GetSampleRateInHz(sample_rate) / 16; } // Variables holding the current mic buffer writing state struct State { u8* sharedmem_buffer = nullptr; u32 sharedmem_size = 0; - size_t size = 0; + std::size_t size = 0; u32 offset = 0; u32 initial_offset = 0; bool looped_buffer = false; u8 sample_size = 0; SampleRate sample_rate = SampleRate::Rate16360; - void UpdateOffset() { - // The last 4 bytes of the shared memory contains the latest offset - // so update that as well https://www.3dbrew.org/wiki/MIC_Shared_Memory - std::memcpy(sharedmem_buffer + (sharedmem_size - sizeof(u32)), - reinterpret_cast(&offset), sizeof(u32)); - } - void WriteSamples(const std::vector& samples) { u32 bytes_total_written = 0; - const size_t remaining_space = size - offset; - size_t bytes_to_write = std::min(samples.size(), remaining_space); + const std::size_t remaining_space = size - offset; + std::size_t bytes_to_write = std::min(samples.size(), remaining_space); // Write as many samples as we can to the buffer. - // TODO if the sample size is 16bit, this could theoretically cut a sample + // TODO if the sample size is 16bit, this could theoretically cut a sample in the case where + // the application configures an odd size std::memcpy(sharedmem_buffer + offset, samples.data(), bytes_to_write); offset += static_cast(bytes_to_write); bytes_total_written += static_cast(bytes_to_write); @@ -105,6 +89,12 @@ struct State { bytes_to_write); offset += static_cast(bytes_to_write); } + + // The last 4 bytes of the shared memory contains the latest offset + // so update that as well https://www.3dbrew.org/wiki/MIC_Shared_Memory + u32_le off = offset; + std::memcpy(sharedmem_buffer + (sharedmem_size - sizeof(u32)), reinterpret_cast(&off), + sizeof(u32)); } }; @@ -112,9 +102,10 @@ struct MIC_U::Impl { explicit Impl(Core::System& system) : timing(system.CoreTiming()) { buffer_full_event = system.Kernel().CreateEvent(Kernel::ResetType::OneShot, "MIC_U::buffer_full_event"); - buffer_write_event = timing.RegisterEvent( - "MIC_U::UpdateBuffer", std::bind(&Impl::UpdateSharedMemBuffer, this, - std::placeholders::_1, std::placeholders::_2)); + buffer_write_event = + timing.RegisterEvent("MIC_U::UpdateBuffer", [this](u64 userdata, s64 cycles_late) { + UpdateSharedMemBuffer(userdata, cycles_late); + }); } void MapSharedMem(Kernel::HLERequestContext& ctx) { @@ -131,7 +122,7 @@ struct MIC_U::Impl { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); - LOG_TRACE(Service_MIC, "MIC:U MapSharedMem called, size=0x{:X}", size); + LOG_TRACE(Service_MIC, "MapSharedMem called, size=0x{:X}", size); } void UnmapSharedMem(Kernel::HLERequestContext& ctx) { @@ -139,7 +130,7 @@ struct MIC_U::Impl { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); shared_memory = nullptr; rb.Push(RESULT_SUCCESS); - LOG_TRACE(Service_MIC, "MIC:U UnmapSharedMem called"); + LOG_TRACE(Service_MIC, "UnmapSharedMem called"); } void UpdateSharedMemBuffer(u64 userdata, s64 cycles_late) { @@ -152,8 +143,6 @@ struct MIC_U::Impl { if (!samples.empty()) { // write the samples to sharedmem page state.WriteSamples(samples); - // write the new offset to the last 4 bytes of the buffer - state.UpdateOffset(); } // schedule next run @@ -194,7 +183,7 @@ struct MIC_U::Impl { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); LOG_TRACE(Service_MIC, - "MIC:U StartSampling called, encoding={}, sample_rate={}, " + "StartSampling called, encoding={}, sample_rate={}, " "audio_buffer_offset={}, audio_buffer_size={}, audio_buffer_loop={}", static_cast(encoding), static_cast(sample_rate), audio_buffer_offset, audio_buffer_size, audio_buffer_loop); @@ -207,8 +196,7 @@ struct MIC_U::Impl { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); - LOG_TRACE(Service_MIC, "MIC:U AdjustSampling sample_rate={}", - static_cast(sample_rate)); + LOG_TRACE(Service_MIC, "AdjustSampling sample_rate={}", static_cast(sample_rate)); } void StopSampling(Kernel::HLERequestContext& ctx) { @@ -217,7 +205,8 @@ struct MIC_U::Impl { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); mic->StopSampling(); - LOG_TRACE(Service_MIC, "MIC:U StopSampling called"); + timing.RemoveEvent(buffer_write_event); + LOG_TRACE(Service_MIC, "StopSampling called"); } void IsSampling(Kernel::HLERequestContext& ctx) { @@ -227,7 +216,7 @@ struct MIC_U::Impl { rb.Push(RESULT_SUCCESS); bool is_sampling = mic->IsSampling(); rb.Push(is_sampling); - LOG_TRACE(Service_MIC, "MIC:U IsSampling: {}", is_sampling); + LOG_TRACE(Service_MIC, "IsSampling: {}", is_sampling); } void GetBufferFullEvent(Kernel::HLERequestContext& ctx) { @@ -246,7 +235,7 @@ struct MIC_U::Impl { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); - LOG_TRACE(Service_MIC, "MIC:U SetGain gain={}", gain); + LOG_TRACE(Service_MIC, "SetGain gain={}", gain); } void GetGain(Kernel::HLERequestContext& ctx) { @@ -256,7 +245,7 @@ struct MIC_U::Impl { rb.Push(RESULT_SUCCESS); u8 gain = mic->GetGain(); rb.Push(gain); - LOG_TRACE(Service_MIC, "MIC:U GetGain gain={}", gain); + LOG_TRACE(Service_MIC, "GetGain gain={}", gain); } void SetPower(Kernel::HLERequestContext& ctx) { @@ -266,7 +255,7 @@ struct MIC_U::Impl { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); - LOG_TRACE(Service_MIC, "MIC:U SetPower mic_power={}", power); + LOG_TRACE(Service_MIC, "SetPower mic_power={}", power); } void GetPower(Kernel::HLERequestContext& ctx) { @@ -276,7 +265,7 @@ struct MIC_U::Impl { rb.Push(RESULT_SUCCESS); bool mic_power = mic->GetPower(); rb.Push(mic_power); - LOG_TRACE(Service_MIC, "MIC:U GetPower called"); + LOG_TRACE(Service_MIC, "GetPower called"); } void SetIirFilterMic(Kernel::HLERequestContext& ctx) { @@ -437,3 +426,19 @@ void InstallInterfaces(Core::System& system) { } } // namespace Service::MIC + +namespace Frontend::Mic { +static std::shared_ptr current_mic; + +void RegisterMic(std::shared_ptr mic) { + current_mic = mic; +} + +std::shared_ptr GetCurrentMic() { + if (!current_mic) { + current_mic = std::make_shared(); + } + return current_mic; +} + +} // namespace Frontend::Mic diff --git a/src/core/hle/service/mic_u.h b/src/core/hle/service/mic_u.h index bdd94fdd9..bc4933229 100644 --- a/src/core/hle/service/mic_u.h +++ b/src/core/hle/service/mic_u.h @@ -192,6 +192,4 @@ private: void InstallInterfaces(Core::System& system); -void ChangeMicImpl(); - } // namespace Service::MIC diff --git a/src/core/settings.cpp b/src/core/settings.cpp index cd810f2db..39a56e567 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -62,13 +62,13 @@ void Apply() { } // TODO support mic hotswapping by creating the new impl, and copying any parameters to it. switch (Settings::values.mic_input_type) { - case 0: + case Settings::MicInputType::None: Frontend::Mic::RegisterMic(std::make_shared()); break; - case 1: + case Settings::MicInputType::Real: Frontend::Mic::RegisterMic(std::make_shared()); break; - case 2: + case Settings::MicInputType::Static: Frontend::Mic::RegisterMic(std::make_shared()); break; } diff --git a/src/core/settings.h b/src/core/settings.h index 702dd4d21..ef5e48410 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -26,6 +26,12 @@ enum class LayoutOption { SideScreen, }; +enum class MicInputType { + None, + Real, + Static, +}; + namespace NativeButton { enum Values { A, @@ -167,7 +173,7 @@ struct Values { bool enable_audio_stretching; std::string audio_device_id; float volume; - u8 mic_input_type; + MicInputType mic_input_type; std::string mic_input_device; // Camera From ba4dfe42177150b73e607d1ca356b0b136d3e921 Mon Sep 17 00:00:00 2001 From: James Rowe Date: Wed, 6 Mar 2019 11:17:32 -0700 Subject: [PATCH 05/11] Increase mic stream volume to max in case the users mic is set low for some reason --- src/audio_core/cubeb_input.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/audio_core/cubeb_input.cpp b/src/audio_core/cubeb_input.cpp index 5666b09bb..8d1af2b2b 100644 --- a/src/audio_core/cubeb_input.cpp +++ b/src/audio_core/cubeb_input.cpp @@ -79,6 +79,10 @@ void CubebInput::StartSampling(const Frontend::Mic::Parameters& params) { } cubeb_stream_start(impl->stream); + int ret = cubeb_stream_set_volume(impl->stream, 1.0); + if (ret == CUBEB_ERROR_NOT_SUPPORTED) { + LOG_WARNING(Audio, "Unabled to set volume for cubeb input"); + } } void CubebInput::StopSampling() { From 5c61d5304783deb7a755502ae59825d5e98f9492 Mon Sep 17 00:00:00 2001 From: James Rowe Date: Wed, 6 Mar 2019 11:30:23 -0700 Subject: [PATCH 06/11] Fix compiler error --- src/audio_core/cubeb_input.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/audio_core/cubeb_input.cpp b/src/audio_core/cubeb_input.cpp index 8d1af2b2b..f191fd8cd 100644 --- a/src/audio_core/cubeb_input.cpp +++ b/src/audio_core/cubeb_input.cpp @@ -114,7 +114,8 @@ long CubebInput::Impl::DataCallback(cubeb_stream* stream, void* user_data, const } u8 const* data = reinterpret_cast(input_buffer); - impl->sample_queue->Push(std::vector(data, data + num_frames * impl->sample_size_in_bytes)); + std::vector samples{data, data + num_frames * impl->sample_size_in_bytes}; + impl->sample_queue->Push(samples); // returning less than num_frames here signals cubeb to stop sampling return num_frames; From 182d672c15ab2c0e4ffb66f219ce7a02f56d1374 Mon Sep 17 00:00:00 2001 From: James Rowe Date: Wed, 6 Mar 2019 20:03:22 -0700 Subject: [PATCH 07/11] Add ifdef around cubeb for android. Address a few review comments --- src/audio_core/CMakeLists.txt | 8 ++------ src/citra_qt/configuration/configure_audio.cpp | 5 ++++- src/core/frontend/mic.cpp | 5 ++--- src/core/hle/service/mic_u.cpp | 17 ++++++----------- src/core/settings.cpp | 4 ++++ 5 files changed, 18 insertions(+), 21 deletions(-) diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index fc298323f..caddd0a3a 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -2,10 +2,6 @@ add_library(audio_core STATIC audio_types.h codec.cpp codec.h - cubeb_input.cpp - cubeb_input.h - cubeb_sink.cpp - cubeb_sink.h dsp_interface.cpp dsp_interface.h hle/adts.h @@ -34,7 +30,7 @@ add_library(audio_core STATIC time_stretch.h $<$:sdl2_sink.cpp sdl2_sink.h> - $<$:cubeb_sink.cpp cubeb_sink.h> + $<$:cubeb_sink.cpp cubeb_sink.h cubeb_input.cpp cubeb_input.h> $<$:hle/ffmpeg_decoder.cpp hle/ffmpeg_decoder.h hle/ffmpeg_dl.cpp hle/ffmpeg_dl.h> $<$:hle/wmf_decoder.cpp hle/wmf_decoder.h hle/wmf_decoder_utils.cpp hle/wmf_decoder_utils.h> ) @@ -65,6 +61,6 @@ endif() if(ENABLE_CUBEB) target_link_libraries(audio_core PRIVATE cubeb) - add_definitions(-DHAVE_CUBEB=1) + target_compile_definitions(audio_core PUBLIC HAVE_CUBEB) endif() diff --git a/src/citra_qt/configuration/configure_audio.cpp b/src/citra_qt/configuration/configure_audio.cpp index e84c2f14d..e9d700c9b 100644 --- a/src/citra_qt/configuration/configure_audio.cpp +++ b/src/citra_qt/configuration/configure_audio.cpp @@ -4,7 +4,9 @@ #include #include +#ifdef HAVE_CUBEB #include "audio_core/cubeb_input.h" +#endif #include "audio_core/sink.h" #include "audio_core/sink_details.h" #include "citra_qt/configuration/configure_audio.h" @@ -32,10 +34,11 @@ ConfigureAudio::ConfigureAudio(QWidget* parent) ui->input_device_combo_box->clear(); ui->input_device_combo_box->addItem(tr("Default")); +#ifdef HAVE_CUBEB for (const auto& device : AudioCore::ListCubebInputDevices()) { ui->input_device_combo_box->addItem(QString::fromStdString(device)); } - +#endif connect(ui->input_type_combo_box, qOverload(&QComboBox::currentIndexChanged), this, &ConfigureAudio::updateAudioInputDevices); diff --git a/src/core/frontend/mic.cpp b/src/core/frontend/mic.cpp index 84f090cb2..8f2b4846a 100644 --- a/src/core/frontend/mic.cpp +++ b/src/core/frontend/mic.cpp @@ -8,9 +8,8 @@ namespace Frontend::Mic { -constexpr std::array NOISE_SAMPLE_8_BIT = { - 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF5, 0xFF, 0xFF, 0xFF, 0xFF, 0x8E, 0xFF, - 0xF4, 0xE1, 0xBF, 0x9A, 0x71, 0x58, 0x5B, 0x5F, 0x62, 0xC2, 0x25, 0x05, 0x01, 0x01, 0x01, 0x01}; +constexpr std::array NOISE_SAMPLE_8_BIT = {0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF5, 0xFF, 0xFF, 0xFF, 0xFF, 0x8E, 0xFF}; constexpr std::array NOISE_SAMPLE_16_BIT = { 0x64, 0x61, 0x74, 0x61, 0x56, 0xD7, 0x00, 0x00, 0x48, 0xF7, 0x86, 0x05, 0x77, 0x1A, 0xF4, 0x1F, diff --git a/src/core/hle/service/mic_u.cpp b/src/core/hle/service/mic_u.cpp index 0ac6a1340..709844676 100644 --- a/src/core/hle/service/mic_u.cpp +++ b/src/core/hle/service/mic_u.cpp @@ -47,13 +47,8 @@ constexpr u32 GetSampleRateInHz(SampleRate sample_rate) { } } -// The following buffer write rates were found by hardware test on o3ds and n3ds. -// The 3ds writes to the sharedmem roughly every 15 samples -constexpr u64 BufferUpdateRate8180 = BASE_CLOCK_RATE_ARM11 / 511; -constexpr u64 BufferUpdateRate10910 = BASE_CLOCK_RATE_ARM11 / 681; -constexpr u64 BufferUpdateRate16360 = BASE_CLOCK_RATE_ARM11 / 1022; -constexpr u64 BufferUpdateRate32730 = BASE_CLOCK_RATE_ARM11 / 2045; - +// The 3ds hardware was tested to write to the sharedmem every 15 samples regardless of sample_rate. +// So we can just divide the sample rate by 16 and that'll give the correct timing for the event constexpr u64 GetBufferUpdateRate(SampleRate sample_rate) { return GetSampleRateInHz(sample_rate) / 16; } @@ -183,7 +178,7 @@ struct MIC_U::Impl { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); LOG_TRACE(Service_MIC, - "StartSampling called, encoding={}, sample_rate={}, " + "called, encoding={}, sample_rate={}, " "audio_buffer_offset={}, audio_buffer_size={}, audio_buffer_loop={}", static_cast(encoding), static_cast(sample_rate), audio_buffer_offset, audio_buffer_size, audio_buffer_loop); @@ -196,7 +191,7 @@ struct MIC_U::Impl { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); - LOG_TRACE(Service_MIC, "AdjustSampling sample_rate={}", static_cast(sample_rate)); + LOG_TRACE(Service_MIC, "sample_rate={}", static_cast(sample_rate)); } void StopSampling(Kernel::HLERequestContext& ctx) { @@ -206,7 +201,7 @@ struct MIC_U::Impl { rb.Push(RESULT_SUCCESS); mic->StopSampling(); timing.RemoveEvent(buffer_write_event); - LOG_TRACE(Service_MIC, "StopSampling called"); + LOG_TRACE(Service_MIC, "called"); } void IsSampling(Kernel::HLERequestContext& ctx) { @@ -265,7 +260,7 @@ struct MIC_U::Impl { rb.Push(RESULT_SUCCESS); bool mic_power = mic->GetPower(); rb.Push(mic_power); - LOG_TRACE(Service_MIC, "GetPower called"); + LOG_TRACE(Service_MIC, "called"); } void SetIirFilterMic(Kernel::HLERequestContext& ctx) { diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 39a56e567..9574e7bde 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -3,7 +3,9 @@ // Refer to the license.txt file included. #include +#if HAVE_CUBEB #include "audio_core/cubeb_input.h" +#endif #include "audio_core/dsp_interface.h" #include "core/core.h" #include "core/frontend/emu_window.h" @@ -66,7 +68,9 @@ void Apply() { Frontend::Mic::RegisterMic(std::make_shared()); break; case Settings::MicInputType::Real: +#if HAVE_CUBEB Frontend::Mic::RegisterMic(std::make_shared()); +#endif break; case Settings::MicInputType::Static: Frontend::Mic::RegisterMic(std::make_shared()); From f5df13eb24a70f1a37089eed8d4ce8a3e5b848de Mon Sep 17 00:00:00 2001 From: James Rowe Date: Wed, 6 Mar 2019 20:59:58 -0700 Subject: [PATCH 08/11] Remove global state and add mic hot swapping --- src/audio_core/cubeb_input.cpp | 2 +- .../configuration/configure_audio.cpp | 10 +-- src/core/frontend/mic.h | 10 +-- src/core/hle/service/mic_u.cpp | 74 ++++++++++++++----- src/core/hle/service/mic_u.h | 4 + src/core/settings.cpp | 23 ++---- 6 files changed, 71 insertions(+), 52 deletions(-) diff --git a/src/audio_core/cubeb_input.cpp b/src/audio_core/cubeb_input.cpp index f191fd8cd..9e83125e7 100644 --- a/src/audio_core/cubeb_input.cpp +++ b/src/audio_core/cubeb_input.cpp @@ -113,7 +113,7 @@ long CubebInput::Impl::DataCallback(cubeb_stream* stream, void* user_data, const return 0; } - u8 const* data = reinterpret_cast(input_buffer); + const u8* data = reinterpret_cast(input_buffer); std::vector samples{data, data + num_frames * impl->sample_size_in_bytes}; impl->sample_queue->Push(samples); diff --git a/src/citra_qt/configuration/configure_audio.cpp b/src/citra_qt/configuration/configure_audio.cpp index e9d700c9b..6c2fd09fd 100644 --- a/src/citra_qt/configuration/configure_audio.cpp +++ b/src/citra_qt/configuration/configure_audio.cpp @@ -42,9 +42,6 @@ ConfigureAudio::ConfigureAudio(QWidget* parent) connect(ui->input_type_combo_box, qOverload(&QComboBox::currentIndexChanged), this, &ConfigureAudio::updateAudioInputDevices); - ui->input_type_combo_box->setEnabled(!Core::System::GetInstance().IsPoweredOn()); - ui->input_device_combo_box->setEnabled(!Core::System::GetInstance().IsPoweredOn()); - this->setConfiguration(); connect(ui->output_sink_combo_box, qOverload(&QComboBox::currentIndexChanged), this, &ConfigureAudio::updateAudioOutputDevices); @@ -142,12 +139,7 @@ void ConfigureAudio::updateAudioOutputDevices(int sink_index) { } } -void ConfigureAudio::updateAudioInputDevices(int index) { - // TODO: Don't hardcode this to the index for "Real Device" without making it a constant - // somewhere - ui->input_device_combo_box->setEnabled(index == 1 && - !Core::System::GetInstance().IsPoweredOn()); -} +void ConfigureAudio::updateAudioInputDevices(int index) {} void ConfigureAudio::retranslateUi() { ui->retranslateUi(this); diff --git a/src/core/frontend/mic.h b/src/core/frontend/mic.h index 296e434f6..1605e77c0 100644 --- a/src/core/frontend/mic.h +++ b/src/core/frontend/mic.h @@ -46,8 +46,10 @@ public: */ virtual Samples Read() = 0; - /// Adjusts the Parameters. Implementations should update the parameters field in addition to - /// changing the mic to sample according to the new parameters. Called by Core + /** + * Adjusts the Parameters. Implementations should update the parameters field in addition to + * changing the mic to sample according to the new parameters. Called by Core + */ virtual void AdjustSampleRate(u32 sample_rate) = 0; /// Value from 0 - 100 to adjust the mic gain setting. Called by Core @@ -111,8 +113,4 @@ private: std::vector CACHE_16_BIT; }; -void RegisterMic(std::shared_ptr mic); - -std::shared_ptr GetCurrentMic(); - } // namespace Frontend::Mic diff --git a/src/core/hle/service/mic_u.cpp b/src/core/hle/service/mic_u.cpp index 709844676..ca2f06554 100644 --- a/src/core/hle/service/mic_u.cpp +++ b/src/core/hle/service/mic_u.cpp @@ -2,6 +2,9 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#ifdef HAVE_CUBEB +#include "audio_core/cubeb_input.h" +#endif #include "common/logging/log.h" #include "core/core.h" #include "core/frontend/mic.h" @@ -12,6 +15,7 @@ #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/shared_memory.h" #include "core/hle/service/mic_u.h" +#include "core/settings.h" namespace Service::MIC { @@ -129,6 +133,9 @@ struct MIC_U::Impl { } void UpdateSharedMemBuffer(u64 userdata, s64 cycles_late) { + if (change_mic_impl_requested.exchange(false)) { + CreateMic(); + } // If the event was scheduled before the application requested the mic to stop sampling if (!mic->IsSampling()) { return; @@ -311,13 +318,49 @@ struct MIC_U::Impl { rb.Push(RESULT_SUCCESS); } + void CreateMic() { + std::unique_ptr new_mic; + switch (Settings::values.mic_input_type) { + case Settings::MicInputType::None: + new_mic = std::make_unique(); + break; + case Settings::MicInputType::Real: +#if HAVE_CUBEB + new_mic = std::make_unique(); +#else + new_mic = std::make_unique(); +#endif + break; + case Settings::MicInputType::Static: + new_mic = std::make_unique(); + break; + default: + LOG_CRITICAL(Audio, "Mic type not found. Defaulting to null mic"); + new_mic = std::make_unique(); + } + // If theres already a mic, copy over any data to the new mic impl + if (mic) { + new_mic->SetGain(mic->GetGain()); + new_mic->SetPower(mic->GetPower()); + auto params = mic->GetParameters(); + if (mic->IsSampling()) { + mic->StopSampling(); + new_mic->StartSampling(params); + } + } + + mic = std::move(new_mic); + change_mic_impl_requested.store(false); + } + + std::atomic change_mic_impl_requested = false; Kernel::SharedPtr buffer_full_event; Core::TimingEventType* buffer_write_event = nullptr; Kernel::SharedPtr shared_memory; u32 client_version = 0; bool allow_shell_closed = false; bool clamp = false; - std::shared_ptr mic; + std::unique_ptr mic; Core::Timing& timing; State state{}; }; @@ -407,7 +450,7 @@ MIC_U::MIC_U(Core::System& system) {0x00100040, &MIC_U::SetClientVersion, "SetClientVersion"}, }; - impl->mic = Frontend::Mic::GetCurrentMic(); + impl->CreateMic(); RegisterHandlers(functions); } @@ -415,25 +458,20 @@ MIC_U::~MIC_U() { impl->mic->StopSampling(); } +void MIC_U::ReloadMic() { + impl->change_mic_impl_requested.store(true); +} + +void ReloadMic(Core::System& system) { + auto micu = system.ServiceManager().GetService("mic:u"); + if (!micu) + return; + micu->ReloadMic(); +} + void InstallInterfaces(Core::System& system) { auto& service_manager = system.ServiceManager(); std::make_shared(system)->InstallAsService(service_manager); } } // namespace Service::MIC - -namespace Frontend::Mic { -static std::shared_ptr current_mic; - -void RegisterMic(std::shared_ptr mic) { - current_mic = mic; -} - -std::shared_ptr GetCurrentMic() { - if (!current_mic) { - current_mic = std::make_shared(); - } - return current_mic; -} - -} // namespace Frontend::Mic diff --git a/src/core/hle/service/mic_u.h b/src/core/hle/service/mic_u.h index bc4933229..2e40ed404 100644 --- a/src/core/hle/service/mic_u.h +++ b/src/core/hle/service/mic_u.h @@ -19,6 +19,8 @@ public: explicit MIC_U(Core::System& system); ~MIC_U(); + void ReloadMic(); + private: /** * MIC::MapSharedMem service function @@ -190,6 +192,8 @@ private: std::unique_ptr impl; }; +void ReloadMic(Core::System& system); + void InstallInterfaces(Core::System& system); } // namespace Service::MIC diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 9574e7bde..c7311e268 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -3,17 +3,14 @@ // Refer to the license.txt file included. #include -#if HAVE_CUBEB -#include "audio_core/cubeb_input.h" -#endif #include "audio_core/dsp_interface.h" #include "core/core.h" #include "core/frontend/emu_window.h" -#include "core/frontend/mic.h" #include "core/gdbstub/gdbstub.h" #include "core/hle/service/hid/hid.h" #include "core/hle/service/ir/ir_rst.h" #include "core/hle/service/ir/ir_user.h" +#include "core/hle/service/mic_u.h" #include "core/settings.h" #include "video_core/renderer_base.h" #include "video_core/video_core.h" @@ -61,20 +58,8 @@ void Apply() { if (cam) { cam->ReloadCameraDevices(); } - } - // TODO support mic hotswapping by creating the new impl, and copying any parameters to it. - switch (Settings::values.mic_input_type) { - case Settings::MicInputType::None: - Frontend::Mic::RegisterMic(std::make_shared()); - break; - case Settings::MicInputType::Real: -#if HAVE_CUBEB - Frontend::Mic::RegisterMic(std::make_shared()); -#endif - break; - case Settings::MicInputType::Static: - Frontend::Mic::RegisterMic(std::make_shared()); - break; + + Service::MIC::ReloadMic(system); } } @@ -105,6 +90,8 @@ void LogSettings() { LogSetting("Audio_OutputEngine", Settings::values.sink_id); LogSetting("Audio_EnableAudioStretching", Settings::values.enable_audio_stretching); LogSetting("Audio_OutputDevice", Settings::values.audio_device_id); + LogSetting("Audio_InputDeviceType", static_cast(Settings::values.mic_input_type)); + LogSetting("Audio_InputDevice", Settings::values.mic_input_device); using namespace Service::CAM; LogSetting("Camera_OuterRightName", Settings::values.camera_name[OuterRightCamera]); LogSetting("Camera_OuterRightConfig", Settings::values.camera_config[OuterRightCamera]); From f5b86cff52c677d1bf31ec83c45c16a4e022ba54 Mon Sep 17 00:00:00 2001 From: James Rowe Date: Wed, 6 Mar 2019 22:43:35 -0700 Subject: [PATCH 09/11] Clang-format --- src/citra_qt/configuration/config.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index 2f55a4e92..091a85ce2 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -484,7 +484,8 @@ void Config::SaveValues() { WriteSetting("enable_audio_stretching", Settings::values.enable_audio_stretching, true); WriteSetting("output_device", QString::fromStdString(Settings::values.audio_device_id), "auto"); WriteSetting("volume", Settings::values.volume, 1.0f); - WriteSetting("mic_input_device", QString::fromStdString(Settings::values.mic_input_device), "Default"); + WriteSetting("mic_input_device", QString::fromStdString(Settings::values.mic_input_device), + "Default"); WriteSetting("mic_input_type", static_cast(Settings::values.mic_input_type), 0); qt_config->endGroup(); From 9739e2b6fd1df2743562a5d9f202bd08187fc79d Mon Sep 17 00:00:00 2001 From: James Rowe Date: Sun, 17 Mar 2019 22:52:03 -0600 Subject: [PATCH 10/11] Support signed 8bit pcm in cubeb input. Address review --- src/audio_core/cubeb_input.cpp | 32 ++++++++++++++++++++------------ src/core/frontend/mic.cpp | 1 - src/core/hle/service/mic_u.cpp | 10 +++++----- src/core/settings.cpp | 1 - 4 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/audio_core/cubeb_input.cpp b/src/audio_core/cubeb_input.cpp index 9e83125e7..65085053d 100644 --- a/src/audio_core/cubeb_input.cpp +++ b/src/audio_core/cubeb_input.cpp @@ -49,16 +49,12 @@ void CubebInput::StartSampling(const Frontend::Mic::Parameters& params) { LOG_ERROR(Audio, "Application requested unsupported unsigned pcm format. Falling back to signed"); } - if (params.sample_size != 16) { - LOG_ERROR(Audio, - "Application requested unsupported 8 bit pcm format. Falling back to 16 bits"); - } + + impl->sample_size_in_bytes = params.sample_size / 8; parameters = params; is_sampling = true; - impl->sample_size_in_bytes = 2; - cubeb_devid input_device = nullptr; cubeb_stream_params input_params; input_params.channels = 1; @@ -76,12 +72,14 @@ void CubebInput::StartSampling(const Frontend::Mic::Parameters& params) { nullptr, nullptr, latency_frames, Impl::DataCallback, Impl::StateCallback, impl.get()) != CUBEB_OK) { LOG_CRITICAL(Audio, "Error creating cubeb input stream"); + is_sampling = false; + return; } - cubeb_stream_start(impl->stream); - int ret = cubeb_stream_set_volume(impl->stream, 1.0); - if (ret == CUBEB_ERROR_NOT_SUPPORTED) { - LOG_WARNING(Audio, "Unabled to set volume for cubeb input"); + if (cubeb_stream_start(impl->stream) != CUBEB_OK) { + LOG_CRITICAL(Audio, "Error starting cubeb input stream"); + is_sampling = false; + return; } } @@ -113,8 +111,18 @@ long CubebInput::Impl::DataCallback(cubeb_stream* stream, void* user_data, const return 0; } - const u8* data = reinterpret_cast(input_buffer); - std::vector samples{data, data + num_frames * impl->sample_size_in_bytes}; + std::vector samples{}; + samples.reserve(num_frames * impl->sample_size_in_bytes); + if (impl->sample_size_in_bytes == 1) { + // If the sample format is 8bit, then resample back to 8bit before passing back to core + const s16* data = reinterpret_cast(input_buffer); + std::transform(data, data + num_frames, std::back_inserter(samples), + [](s16 sample) { return static_cast(static_cast(sample) >> 8); }); + } else { + // Otherwise copy all of the samples to the buffer (which will be treated as s16 by core) + const u8* data = reinterpret_cast(input_buffer); + samples.insert(samples.begin(), data, data + num_frames * impl->sample_size_in_bytes); + } impl->sample_queue->Push(samples); // returning less than num_frames here signals cubeb to stop sampling diff --git a/src/core/frontend/mic.cpp b/src/core/frontend/mic.cpp index 8f2b4846a..543a23767 100644 --- a/src/core/frontend/mic.cpp +++ b/src/core/frontend/mic.cpp @@ -4,7 +4,6 @@ #include #include "core/frontend/mic.h" -#include "core/hle/service/mic_u.h" namespace Frontend::Mic { diff --git a/src/core/hle/service/mic_u.cpp b/src/core/hle/service/mic_u.cpp index ca2f06554..779b520b8 100644 --- a/src/core/hle/service/mic_u.cpp +++ b/src/core/hle/service/mic_u.cpp @@ -121,7 +121,7 @@ struct MIC_U::Impl { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); - LOG_TRACE(Service_MIC, "MapSharedMem called, size=0x{:X}", size); + LOG_TRACE(Service_MIC, "called, size=0x{:X}", size); } void UnmapSharedMem(Kernel::HLERequestContext& ctx) { @@ -129,7 +129,7 @@ struct MIC_U::Impl { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); shared_memory = nullptr; rb.Push(RESULT_SUCCESS); - LOG_TRACE(Service_MIC, "UnmapSharedMem called"); + LOG_TRACE(Service_MIC, "called"); } void UpdateSharedMemBuffer(u64 userdata, s64 cycles_late) { @@ -237,7 +237,7 @@ struct MIC_U::Impl { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); - LOG_TRACE(Service_MIC, "SetGain gain={}", gain); + LOG_TRACE(Service_MIC, "gain={}", gain); } void GetGain(Kernel::HLERequestContext& ctx) { @@ -247,7 +247,7 @@ struct MIC_U::Impl { rb.Push(RESULT_SUCCESS); u8 gain = mic->GetGain(); rb.Push(gain); - LOG_TRACE(Service_MIC, "GetGain gain={}", gain); + LOG_TRACE(Service_MIC, "gain={}", gain); } void SetPower(Kernel::HLERequestContext& ctx) { @@ -257,7 +257,7 @@ struct MIC_U::Impl { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); - LOG_TRACE(Service_MIC, "SetPower mic_power={}", power); + LOG_TRACE(Service_MIC, "mic_power={}", power); } void GetPower(Kernel::HLERequestContext& ctx) { diff --git a/src/core/settings.cpp b/src/core/settings.cpp index c7311e268..d34aad32a 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -5,7 +5,6 @@ #include #include "audio_core/dsp_interface.h" #include "core/core.h" -#include "core/frontend/emu_window.h" #include "core/gdbstub/gdbstub.h" #include "core/hle/service/hid/hid.h" #include "core/hle/service/ir/ir_rst.h" From b4d538468dc41dfef22f370f706e59ddd35a687e Mon Sep 17 00:00:00 2001 From: James Rowe Date: Wed, 20 Mar 2019 09:25:44 -0600 Subject: [PATCH 11/11] Change resampling s16 -> u8 to respect aliasing rules --- src/audio_core/cubeb_input.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/audio_core/cubeb_input.cpp b/src/audio_core/cubeb_input.cpp index 65085053d..9d3bf2fe8 100644 --- a/src/audio_core/cubeb_input.cpp +++ b/src/audio_core/cubeb_input.cpp @@ -111,13 +111,19 @@ long CubebInput::Impl::DataCallback(cubeb_stream* stream, void* user_data, const return 0; } + constexpr auto resample_s16_s8 = [](s16 sample) { + return static_cast(static_cast(sample) >> 8); + }; + std::vector samples{}; samples.reserve(num_frames * impl->sample_size_in_bytes); if (impl->sample_size_in_bytes == 1) { // If the sample format is 8bit, then resample back to 8bit before passing back to core - const s16* data = reinterpret_cast(input_buffer); - std::transform(data, data + num_frames, std::back_inserter(samples), - [](s16 sample) { return static_cast(static_cast(sample) >> 8); }); + for (std::size_t i; i < num_frames; i++) { + s16 data; + std::memcpy(&data, static_cast(input_buffer) + i * 2, 2); + samples.push_back(resample_s16_s8(data)); + } } else { // Otherwise copy all of the samples to the buffer (which will be treated as s16 by core) const u8* data = reinterpret_cast(input_buffer);