-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
IOS/USB: Emulate Wii Speak #12567
base: master
Are you sure you want to change the base?
IOS/USB: Emulate Wii Speak #12567
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,202 @@ | ||
// Copyright 2024 Dolphin Emulator Project | ||
// SPDX-License-Identifier: GPL-2.0-or-later | ||
|
||
#include <cubeb/cubeb.h> | ||
|
||
#include "AudioCommon/CubebUtils.h" | ||
#include "Common/Logging/Log.h" | ||
|
||
#include "Common/ScopeGuard.h" | ||
#include "Common/Swap.h" | ||
#include "Core/IOS/USB/Emulated/Microphone.h" | ||
|
||
#include <algorithm> | ||
#include <mutex> | ||
|
||
#ifdef _WIN32 | ||
#include <Objbase.h> | ||
#endif | ||
|
||
namespace IOS::HLE::USB | ||
{ | ||
Microphone::Microphone() | ||
{ | ||
StreamInit(); | ||
} | ||
|
||
Microphone::~Microphone() | ||
{ | ||
StreamTerminate(); | ||
|
||
#ifdef _WIN32 | ||
if (m_should_couninit) | ||
{ | ||
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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this referring to the GameCube's Microphone which used the memory card port? If it is a typo, referring to |
||
nullptr, std::max<u32>(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<Microphone*>(user_data); | ||
|
||
std::lock_guard lk(mic->ring_lock); | ||
|
||
const s16* buff_in = static_cast<const s16*>(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; | ||
} | ||
|
||
mic->samples_avail += nframes; | ||
if (mic->samples_avail > mic->stream_size) | ||
{ | ||
mic->samples_avail = 0; | ||
} | ||
|
||
return nframes; | ||
} | ||
|
||
void Microphone::ReadIntoBuffer(u8* dst, u32 size) | ||
{ | ||
std::lock_guard lk(ring_lock); | ||
|
||
if (samples_avail >= buff_size_samples) | ||
{ | ||
u8* last_buffer = reinterpret_cast<u8*>(&stream_buffer[stream_rpos]); | ||
std::memcpy(dst, static_cast<u8*>(last_buffer), size); | ||
|
||
samples_avail -= buff_size_samples; | ||
|
||
stream_rpos += buff_size_samples; | ||
stream_rpos %= stream_size; | ||
} | ||
} | ||
|
||
bool Microphone::HasData() const | ||
{ | ||
return samples_avail > 0; | ||
} | ||
} // namespace IOS::HLE::USB |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
// Copyright 2024 Dolphin Emulator Project | ||
// SPDX-License-Identifier: GPL-2.0-or-later | ||
|
||
#pragma once | ||
|
||
#include <mutex> | ||
#include <string> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This |
||
#include <vector> | ||
|
||
#include "Common/CommonTypes.h" | ||
#include "Common/Event.h" | ||
#include "Common/WorkQueueThread.h" | ||
|
||
struct cubeb; | ||
struct cubeb_stream; | ||
|
||
namespace IOS::HLE::USB | ||
{ | ||
class Microphone final | ||
{ | ||
public: | ||
Microphone(); | ||
~Microphone(); | ||
|
||
void StreamInit(); | ||
void StreamTerminate(); | ||
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(); | ||
|
||
static constexpr u32 SAMPLING_RATE = 8000; | ||
static constexpr u32 BUFFER_SIZE = SAMPLING_RATE / 2; | ||
|
||
s16* stream_buffer; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this be a unique_ptr? This doesn't seem to actually get freed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought I deleted it in the destructor, I guess not. unique_ptr would be better though. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't a buffer allocated on the stack (for instance, using a |
||
std::mutex ring_lock; | ||
std::shared_ptr<cubeb> m_cubeb_ctx = nullptr; | ||
cubeb_stream* m_cubeb_stream = nullptr; | ||
std::vector<u8> m_temp_buffer{}; | ||
|
||
int stream_size; | ||
int stream_wpos; | ||
int stream_rpos; | ||
int samples_avail; | ||
int buff_size_samples = 16; | ||
|
||
#ifdef _WIN32 | ||
Common::WorkQueueThread<std::function<void()>> m_work_queue; | ||
bool m_coinit_success = false; | ||
bool m_should_couninit = false; | ||
#endif | ||
}; | ||
} // namespace IOS::HLE::USB |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't
stream_size
andbuff_size_samples
be const?