Convert to cubeb
This commit is contained in:
parent
f43f3a0bfd
commit
c308aa817c
|
@ -2,48 +2,180 @@
|
|||
// Created by Noah Pistilli on 2023-07-09.
|
||||
//
|
||||
|
||||
#include <cubeb/cubeb.h>
|
||||
|
||||
#include "AudioCommon/CubebUtils.h"
|
||||
#include <Common/Logging/Log.h>
|
||||
|
||||
#include "Microphone.h"
|
||||
#include "Common/swap.h"
|
||||
#include "Common/Swap.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <mutex>
|
||||
|
||||
namespace IOS::HLE::USB
|
||||
{
|
||||
std::vector<std::string> Microphone::ListDevices()
|
||||
{
|
||||
std::vector<std::string> 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<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;
|
||||
}
|
||||
|
||||
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<int>(alcGetError(m_device));
|
||||
}
|
||||
|
||||
int Microphone::StartCapture()
|
||||
{
|
||||
alcCaptureStart(m_device);
|
||||
return static_cast<int>(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<u8*>(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<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 m_num_of_samples != 0;
|
||||
return samples_avail > 0;
|
||||
}
|
||||
} // namespace IOS::HLE::USB
|
|
@ -5,106 +5,55 @@
|
|||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
struct cubeb;
|
||||
struct cubeb_stream;
|
||||
|
||||
namespace IOS::HLE::USB
|
||||
{
|
||||
template <size_t S>
|
||||
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<u8> m_container;
|
||||
u32 m_head = 0, m_tail = 0, m_used = 0;
|
||||
};
|
||||
|
||||
class Microphone final
|
||||
{
|
||||
public:
|
||||
static std::vector<std::string> 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<u8> m_dsp_data{};
|
||||
s16* stream_buffer;
|
||||
std::mutex ring_lock;
|
||||
std::shared_ptr<cubeb> m_cubeb_ctx = nullptr;
|
||||
cubeb_stream* m_cubeb_stream = nullptr;
|
||||
std::vector<u8> m_temp_buffer{};
|
||||
simple_ringbuf<BUFFER_SIZE> m_rbuf_dsp;
|
||||
|
||||
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
|
||||
|
|
|
@ -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::microseconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch())
|
||||
.count() %
|
||||
TIMESTEP);
|
||||
m_microphone.PerformAudioCapture();
|
||||
m_microphone.GetSoundData();
|
||||
}
|
||||
});
|
||||
m_microphone = std::make_unique<Microphone>();
|
||||
}
|
||||
|
||||
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<IntrMessage> cmd)
|
|||
|
||||
int WiiSpeak::SubmitTransfer(std::unique_ptr<IsoMessage> 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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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<Microphone> m_microphone;
|
||||
DeviceDescriptor m_device_descriptor{};
|
||||
std::vector<ConfigDescriptor> m_config_descriptor;
|
||||
std::vector<InterfaceDescriptor> m_interface_descriptor;
|
||||
std::vector<EndpointDescriptor> m_endpoint_descriptor;
|
||||
std::thread m_microphone_thread;
|
||||
std::mutex m_mutex;
|
||||
Common::Event m_shutdown_event;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -30,5 +30,4 @@ private:
|
|||
void EmulateWiiSpeak(bool emulate);
|
||||
|
||||
QCheckBox* m_checkbox;
|
||||
QComboBox* m_combobox_microphones;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue