diff --git a/Source/Core/Core/IOS/USB/Emulated/Microphone.cpp b/Source/Core/Core/IOS/USB/Emulated/Microphone.cpp index 62a886e3b7..cb3d90f00b 100644 --- a/Source/Core/Core/IOS/USB/Emulated/Microphone.cpp +++ b/Source/Core/Core/IOS/USB/Emulated/Microphone.cpp @@ -2,48 +2,180 @@ // Created by Noah Pistilli on 2023-07-09. // +#include + +#include "AudioCommon/CubebUtils.h" +#include + #include "Microphone.h" -#include "Common/swap.h" +#include "Common/Swap.h" #include +#include namespace IOS::HLE::USB { -std::vector Microphone::ListDevices() -{ - std::vector devices{}; - const ALchar* pDeviceList = alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER); - while (*pDeviceList) +Microphone::Microphone() { + StreamInit(); +} + +Microphone::~Microphone() { + StreamTerminate(); + +#ifdef _WIN32 + if (m_should_couninit) { - devices.emplace_back(pDeviceList); - pDeviceList += strlen(pDeviceList) + 1; + Common::Event sync_event; + m_work_queue.EmplaceItem([this, &sync_event] { + Common::ScopeGuard sync_event_guard([&sync_event] { sync_event.Set(); }); + m_should_couninit = false; + CoUninitialize(); + }); + sync_event.Wait(); + } + m_coinit_success = false; +#endif +} + +void Microphone::StreamInit() +{ +#ifdef _WIN32 + if (!m_coinit_success) + return; + Common::Event sync_event; + m_work_queue.EmplaceItem([this, &sync_event] { + Common::ScopeGuard sync_event_guard([&sync_event] { sync_event.Set(); }); +#endif + m_cubeb_ctx = CubebUtils::GetContext(); +#ifdef _WIN32 + }); + sync_event.Wait(); +#endif + + // TODO: Not here but rather inside the WiiSpeak device if possible? + StreamStart(); +} + +void Microphone::StreamTerminate() +{ + StopStream(); + + if (m_cubeb_ctx) + { +#ifdef _WIN32 + if (!m_coinit_success) + return; + Common::Event sync_event; + m_work_queue.EmplaceItem([this, &sync_event] { + Common::ScopeGuard sync_event_guard([&sync_event] { sync_event.Set(); }); +#endif + m_cubeb_ctx.reset(); +#ifdef _WIN32 + }); + sync_event.Wait(); +#endif + } +} + +static void state_callback(cubeb_stream* stream, void* user_data, cubeb_state state) +{ +} + +void Microphone::StreamStart() +{ + if (!m_cubeb_ctx) + return; + +#ifdef _WIN32 + if (!m_coinit_success) + return; + Common::Event sync_event; + m_work_queue.EmplaceItem([this, &sync_event] { + Common::ScopeGuard sync_event_guard([&sync_event] { sync_event.Set(); }); +#endif + stream_size = buff_size_samples * 500; + stream_buffer = new s16[stream_size]; + + cubeb_stream_params params{}; + params.format = CUBEB_SAMPLE_S16LE; + params.rate = SAMPLING_RATE; + params.channels = 1; + params.layout = CUBEB_LAYOUT_MONO; + + u32 minimum_latency; + if (cubeb_get_min_latency(m_cubeb_ctx.get(), ¶ms, &minimum_latency) != CUBEB_OK) + { + WARN_LOG_FMT(EXPANSIONINTERFACE, "Error getting minimum latency"); + } + + if (cubeb_stream_init(m_cubeb_ctx.get(), &m_cubeb_stream, + "Dolphin Emulated GameCube Microphone", nullptr, ¶ms, nullptr, + nullptr, std::max(16, minimum_latency), DataCallback, + state_callback, this) != CUBEB_OK) + { + ERROR_LOG_FMT(IOS_USB, "Error initializing cubeb stream"); + return; + } + + if (cubeb_stream_start(m_cubeb_stream) != CUBEB_OK) + { + ERROR_LOG_FMT(EXPANSIONINTERFACE, "Error starting cubeb stream"); + return; + } + + INFO_LOG_FMT(EXPANSIONINTERFACE, "started cubeb stream"); +#ifdef _WIN32 + }); + sync_event.Wait(); +#endif +} + +void Microphone::StopStream() +{ + if (m_cubeb_stream) + { +#ifdef _WIN32 + Common::Event sync_event; + m_work_queue.EmplaceItem([this, &sync_event] { + Common::ScopeGuard sync_event_guard([&sync_event] { sync_event.Set(); }); +#endif + if (cubeb_stream_stop(m_cubeb_stream) != CUBEB_OK) + ERROR_LOG_FMT(IOS_USB, "Error stopping cubeb stream"); + cubeb_stream_destroy(m_cubeb_stream); + m_cubeb_stream = nullptr; +#ifdef _WIN32 + }); + sync_event.Wait(); +#endif + } +} + +long Microphone::DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer, + void* /*output_buffer*/, long nframes) +{ + auto* mic = static_cast(user_data); + + std::lock_guard lk(mic->ring_lock); + + const s16* buff_in = static_cast(input_buffer); + for (long i = 0; i < nframes; i++) + { + mic->stream_buffer[mic->stream_wpos] = Common::swap16(buff_in[i]); + mic->stream_wpos = (mic->stream_wpos + 1) % mic->stream_size; } - return devices; -} + mic->samples_avail += nframes; + if (mic->samples_avail > mic->stream_size) + { + mic->samples_avail = 0; + } -int Microphone::OpenMicrophone() -{ - m_device = alcCaptureOpenDevice(nullptr, SAMPLING_RATE, AL_FORMAT_MONO16, BUFFER_SIZE); - m_dsp_data.resize(BUFFER_SIZE, 0); - m_temp_buffer.resize(BUFFER_SIZE, 0); - return static_cast(alcGetError(m_device)); -} - -int Microphone::StartCapture() -{ - alcCaptureStart(m_device); - return static_cast(alcGetError(m_device)); -} - -void Microphone::StopCapture() -{ - alcCaptureStop(m_device); + return nframes; } void Microphone::PerformAudioCapture() { - m_num_of_samples = BUFFER_SIZE / 2; + /*m_num_of_samples = BUFFER_SIZE / 2; ALCint samples_in{}; alcGetIntegerv(m_device, ALC_CAPTURE_SAMPLES, 1, &samples_in); @@ -52,7 +184,7 @@ void Microphone::PerformAudioCapture() if (m_num_of_samples == 0) return; - alcCaptureSamples(m_device, m_dsp_data.data(), m_num_of_samples); + alcCaptureSamples(m_device, m_dsp_data.data(), m_num_of_samples);*/ } void Microphone::ByteSwap(const void* src, void* dst) const @@ -62,7 +194,7 @@ void Microphone::ByteSwap(const void* src, void* dst) const void Microphone::GetSoundData() { - if (m_num_of_samples == 0) + /*if (m_num_of_samples == 0) return; u8* ptr = const_cast(m_temp_buffer.data()); @@ -76,16 +208,27 @@ void Microphone::GetSoundData() } } - m_rbuf_dsp.write_bytes(ptr, m_num_of_samples * 2); + m_rbuf_dsp.write_bytes(ptr, m_num_of_samples * 2);*/ } void Microphone::ReadIntoBuffer(u8* dst, u32 size) { - m_rbuf_dsp.read_bytes(dst, size); + std::lock_guard lk(ring_lock); + + if (samples_avail >= buff_size_samples) + { + u8* last_buffer = reinterpret_cast(&stream_buffer[stream_rpos]); + std::memcpy(dst, static_cast(last_buffer), size); + + samples_avail -= buff_size_samples; + + stream_rpos += buff_size_samples; + stream_rpos %= stream_size; + } } bool Microphone::HasData() const { - return m_num_of_samples != 0; + return samples_avail > 0; } } // namespace IOS::HLE::USB \ No newline at end of file diff --git a/Source/Core/Core/IOS/USB/Emulated/Microphone.h b/Source/Core/Core/IOS/USB/Emulated/Microphone.h index daa1d175d6..11db0000bb 100644 --- a/Source/Core/Core/IOS/USB/Emulated/Microphone.h +++ b/Source/Core/Core/IOS/USB/Emulated/Microphone.h @@ -5,106 +5,55 @@ #include #include +#include #include "Common/CommonTypes.h" +struct cubeb; +struct cubeb_stream; + namespace IOS::HLE::USB { -template -class simple_ringbuf -{ -public: - simple_ringbuf() { m_container.resize(S); } - - bool has_data() const { return m_used != 0; } - - u32 read_bytes(u8* buf, const u32 size) - { - u32 to_read = size > m_used ? m_used : size; - if (!to_read) - return 0; - - u8* data = m_container.data(); - u32 new_tail = m_tail + to_read; - - if (new_tail >= S) - { - u32 first_chunk_size = S - m_tail; - std::memcpy(buf, data + m_tail, first_chunk_size); - std::memcpy(buf + first_chunk_size, data, to_read - first_chunk_size); - m_tail = (new_tail - S); - } - else - { - std::memcpy(buf, data + m_tail, to_read); - m_tail = new_tail; - } - - m_used -= to_read; - - return to_read; - } - - void write_bytes(const u8* buf, const u32 size) - { - if (u32 over_size = m_used + size; over_size > S) - { - m_tail += (over_size - S); - if (m_tail > S) - m_tail -= S; - - m_used = S; - } - else - { - m_used = over_size; - } - - u8* data = m_container.data(); - u32 new_head = m_head + size; - - if (new_head >= S) - { - u32 first_chunk_size = S - m_head; - std::memcpy(data + m_head, buf, first_chunk_size); - std::memcpy(data, buf + first_chunk_size, size - first_chunk_size); - m_head = (new_head - S); - } - else - { - std::memcpy(data + m_head, buf, size); - m_head = new_head; - } - } - -protected: - std::vector m_container; - u32 m_head = 0, m_tail = 0, m_used = 0; -}; - class Microphone final { public: - static std::vector ListDevices(); + Microphone(); + ~Microphone(); - int OpenMicrophone(); - int StartCapture(); - void StopCapture(); + void StreamInit(); + void StreamTerminate(); void PerformAudioCapture(); void GetSoundData(); void ReadIntoBuffer(u8* dst, u32 size); bool HasData() const; private: + static long DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer, + void* output_buffer, long nframes); + + void StreamStart(); + void StopStream(); void ByteSwap(const void* src, void* dst) const; static constexpr u32 SAMPLING_RATE = 8000; static constexpr u32 BUFFER_SIZE = SAMPLING_RATE / 2; - ALCdevice* m_device; - u32 m_num_of_samples{}; - std::vector m_dsp_data{}; + s16* stream_buffer; + std::mutex ring_lock; + std::shared_ptr m_cubeb_ctx = nullptr; + cubeb_stream* m_cubeb_stream = nullptr; std::vector m_temp_buffer{}; - simple_ringbuf m_rbuf_dsp; + + int stream_size; + int stream_wpos; + int stream_rpos; + int samples_avail; + int buff_size_samples = 16; + +#ifdef _WIN32 + Common::WorkQueueThread> m_work_queue; + bool m_coinit_success = false; + bool m_should_couninit = false; +#endif }; } // namespace IOS::HLE::USB diff --git a/Source/Core/Core/IOS/USB/Emulated/WiiSpeak.cpp b/Source/Core/Core/IOS/USB/Emulated/WiiSpeak.cpp index 4e763585c3..e328355cad 100644 --- a/Source/Core/Core/IOS/USB/Emulated/WiiSpeak.cpp +++ b/Source/Core/Core/IOS/USB/Emulated/WiiSpeak.cpp @@ -23,52 +23,12 @@ WiiSpeak::WiiSpeak(IOS::HLE::EmulationKernel& ios, const std::string& device_nam m_endpoint_descriptor.emplace_back(EndpointDescriptor{0x7, 0x5, 0x2, 0x2, 0x0020, 0}); m_endpoint_descriptor.emplace_back(EndpointDescriptor{0x7, 0x5, 0x3, 0x1, 0x0040, 1}); - m_microphone = Microphone(); - if (m_microphone.OpenMicrophone() != 0) - { - ERROR_LOG_FMT(IOS_USB, "Error opening the microphone."); - b_is_mic_connected = false; - return; - } - - if (m_microphone.StartCapture() != 0) - { - ERROR_LOG_FMT(IOS_USB, "Error starting captures."); - b_is_mic_connected = false; - return; - } - - m_microphone_thread = std::thread([this] { - u64 timeout{}; - constexpr u64 TIMESTEP = 256ull * 1'000'000ull / 48000ull; - while (true) - { - if (m_shutdown_event.WaitFor(std::chrono::microseconds{timeout})) - return; - - std::lock_guard lg(m_mutex); - timeout = TIMESTEP - (std::chrono::duration_cast( - std::chrono::steady_clock::now().time_since_epoch()) - .count() % - TIMESTEP); - m_microphone.PerformAudioCapture(); - m_microphone.GetSoundData(); - } - }); + m_microphone = std::make_unique(); } WiiSpeak::~WiiSpeak() { - { - std::lock_guard lg(m_mutex); - if (!m_microphone_thread.joinable()) - return; - m_shutdown_event.Set(); - } - - m_microphone_thread.join(); - m_microphone.StopCapture(); } DeviceDescriptor WiiSpeak::GetDeviceDescriptor() const @@ -209,19 +169,16 @@ int WiiSpeak::SubmitTransfer(std::unique_ptr cmd) int WiiSpeak::SubmitTransfer(std::unique_ptr cmd) { - if (!b_is_mic_connected) - return IPC_ENOENT; - auto& system = m_ios.GetSystem(); auto& memory = system.GetMemory(); u8* packets = memory.GetPointer(cmd->data_address); - if (cmd->endpoint == 0x81 && m_microphone.HasData()) - m_microphone.ReadIntoBuffer(packets, cmd->length); + if (cmd->endpoint == 0x81 && m_microphone->HasData()) + m_microphone->ReadIntoBuffer(packets, cmd->length); // Anything more causes the visual cue to not appear. // Anything less is more choppy audio. - cmd->ScheduleTransferCompletion(IPC_SUCCESS, 20000); + cmd->ScheduleTransferCompletion(IPC_SUCCESS, 2500); return IPC_SUCCESS; }; diff --git a/Source/Core/Core/IOS/USB/Emulated/WiiSpeak.h b/Source/Core/Core/IOS/USB/Emulated/WiiSpeak.h index 2b9d468bf8..2034354836 100644 --- a/Source/Core/Core/IOS/USB/Emulated/WiiSpeak.h +++ b/Source/Core/Core/IOS/USB/Emulated/WiiSpeak.h @@ -84,12 +84,11 @@ private: bool m_device_attached = false; bool init = false; bool b_is_mic_connected = true; - Microphone m_microphone; + std::unique_ptr m_microphone; DeviceDescriptor m_device_descriptor{}; std::vector m_config_descriptor; std::vector m_interface_descriptor; std::vector m_endpoint_descriptor; - std::thread m_microphone_thread; std::mutex m_mutex; Common::Event m_shutdown_event; }; diff --git a/Source/Core/DolphinQt/EmulatedUSB/WiiSpeakWindow.cpp b/Source/Core/DolphinQt/EmulatedUSB/WiiSpeakWindow.cpp index f3770047a7..05cbd6d86c 100644 --- a/Source/Core/DolphinQt/EmulatedUSB/WiiSpeakWindow.cpp +++ b/Source/Core/DolphinQt/EmulatedUSB/WiiSpeakWindow.cpp @@ -59,13 +59,6 @@ void WiiSpeakWindow::CreateMainWindow() checkbox_layout->addWidget(m_checkbox); checkbox_group->setLayout(checkbox_layout); - m_combobox_microphones = new QComboBox(); - for (const std::string& device : IOS::HLE::USB::Microphone::ListDevices()) - { - m_combobox_microphones->addItem(QString::fromStdString(device)); - } - - checkbox_layout->addWidget(m_combobox_microphones); main_layout->addWidget(checkbox_group); setLayout(main_layout); diff --git a/Source/Core/DolphinQt/EmulatedUSB/WiiSpeakWindow.h b/Source/Core/DolphinQt/EmulatedUSB/WiiSpeakWindow.h index f37c82be96..df0d6a3bd5 100644 --- a/Source/Core/DolphinQt/EmulatedUSB/WiiSpeakWindow.h +++ b/Source/Core/DolphinQt/EmulatedUSB/WiiSpeakWindow.h @@ -30,5 +30,4 @@ private: void EmulateWiiSpeak(bool emulate); QCheckBox* m_checkbox; - QComboBox* m_combobox_microphones; };