mirror of
https://github.com/ARMSX2/ARMSX2.git
synced 2026-08-24 16:50:16 -07:00
292 lines
7.9 KiB
C++
292 lines
7.9 KiB
C++
// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
|
|
// SPDX-License-Identifier: GPL-3.0+
|
|
|
|
#include "USB/usb-mic/audiodev-cubeb.h"
|
|
#include "USB/USB.h"
|
|
#include "Host.h"
|
|
#include "common/Assertions.h"
|
|
#include "common/Console.h"
|
|
#include "common/Error.h"
|
|
|
|
#include "cubeb/cubeb.h"
|
|
#include "fmt/format.h"
|
|
|
|
#ifdef _WIN32
|
|
#include "common/RedtapeWindows.h"
|
|
#include <objbase.h>
|
|
|
|
#include "wil/resource.h"
|
|
#endif
|
|
|
|
// Since the context gets used to populate the device list, that unfortunately means
|
|
// we need locking around it, since the UI thread's gonna be saying hi. The settings
|
|
// callbacks don't actually modify the context itself, though, only look at the
|
|
// device list.
|
|
static cubeb* s_cubeb_context;
|
|
static cubeb_device_collection s_cubeb_input_devices;
|
|
static cubeb_device_collection s_cubeb_output_devices;
|
|
static u32 s_cubeb_refcount = 0;
|
|
static std::mutex s_cubeb_context_mutex;
|
|
|
|
static cubeb* GetCubebContext(const char* backend = nullptr)
|
|
{
|
|
std::lock_guard lock(s_cubeb_context_mutex);
|
|
|
|
#ifdef _WIN32
|
|
// For enumeration, we need *any* COM context. multi- or single-threaded.
|
|
// As COM is per-thread, initialize and tear it down every time.
|
|
// Doing it only on 0 refcount is a bad idea, because the Cubeb context could be created on one thread, and destroyed on another.
|
|
HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
|
if (hr == RPC_E_CHANGED_MODE)
|
|
{
|
|
hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
|
|
}
|
|
if (FAILED(hr))
|
|
{
|
|
Console.ErrorFmt("CoInitializeEx failed: {}", Error::CreateHResult(hr).GetDescription());
|
|
return nullptr;
|
|
}
|
|
wil::unique_couninitialize_call uninit;
|
|
#endif
|
|
|
|
if (!s_cubeb_context)
|
|
{
|
|
pxAssert(s_cubeb_refcount == 0);
|
|
const int res = cubeb_init(&s_cubeb_context, "PCSX2_USB", backend);
|
|
if (res != CUBEB_OK)
|
|
{
|
|
Console.Error("cubeb_init() failed: %d", res);
|
|
return nullptr;
|
|
}
|
|
|
|
cubeb_enumerate_devices(s_cubeb_context, CUBEB_DEVICE_TYPE_INPUT, &s_cubeb_input_devices);
|
|
cubeb_enumerate_devices(s_cubeb_context, CUBEB_DEVICE_TYPE_OUTPUT, &s_cubeb_output_devices);
|
|
}
|
|
|
|
if (s_cubeb_context)
|
|
s_cubeb_refcount++;
|
|
|
|
#ifdef _WIN32
|
|
// ReleaseCubebContext will call CoUninitialize
|
|
uninit.release();
|
|
#endif
|
|
|
|
return s_cubeb_context;
|
|
}
|
|
|
|
static void ReleaseCubebContext()
|
|
{
|
|
std::lock_guard lock(s_cubeb_context_mutex);
|
|
|
|
pxAssert(s_cubeb_refcount > 0);
|
|
if ((--s_cubeb_refcount) == 0)
|
|
{
|
|
cubeb_device_collection_destroy(s_cubeb_context, &s_cubeb_input_devices);
|
|
s_cubeb_input_devices = {};
|
|
cubeb_device_collection_destroy(s_cubeb_context, &s_cubeb_output_devices);
|
|
s_cubeb_output_devices = {};
|
|
|
|
cubeb_destroy(s_cubeb_context);
|
|
s_cubeb_context = nullptr;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
CoUninitialize();
|
|
#endif
|
|
}
|
|
|
|
static cubeb_devid FindCubebDevice(const char* devname, bool input)
|
|
{
|
|
if (std::strcmp(devname, "cubeb_default") == 0)
|
|
return nullptr;
|
|
|
|
const cubeb_device_collection& col = input ? s_cubeb_input_devices : s_cubeb_output_devices;
|
|
for (size_t i = 0; i < col.count; i++)
|
|
{
|
|
if (std::strcmp(devname, col.device[i].device_id) == 0)
|
|
return col.device[i].devid;
|
|
}
|
|
|
|
Console.Warning("(audiodev_cubeb) Unable to find %s device %s", input ? "input" : "output", devname);
|
|
return nullptr;
|
|
}
|
|
|
|
static void CubebStateCallback(cubeb_stream* stream, void* user_ptr, cubeb_state state)
|
|
{
|
|
}
|
|
|
|
namespace usb_mic
|
|
{
|
|
namespace audiodev_cubeb
|
|
{
|
|
CubebAudioDevice::CubebAudioDevice(AudioDir dir, u32 channels, std::string devname, s32 latency)
|
|
: AudioDevice(dir, channels)
|
|
, mLatency(latency)
|
|
, mDeviceName(std::move(devname))
|
|
{
|
|
mContext = GetCubebContext();
|
|
mDeviceId = FindCubebDevice(mDeviceName.c_str(), (dir == AUDIODIR_SOURCE));
|
|
}
|
|
|
|
CubebAudioDevice::~CubebAudioDevice()
|
|
{
|
|
if (mStream)
|
|
CubebAudioDevice::Stop();
|
|
|
|
if (mContext)
|
|
ReleaseCubebContext();
|
|
}
|
|
|
|
std::vector<std::pair<std::string, std::string>> CubebAudioDevice::GetDeviceList(bool input)
|
|
{
|
|
std::vector<std::pair<std::string, std::string>> ret;
|
|
ret.emplace_back("", TRANSLATE_SV("USB", "Not Connected"));
|
|
ret.emplace_back("cubeb_default", input ? TRANSLATE_SV("USB", "Default Input Device") : TRANSLATE_SV("USB", "Default Output Device"));
|
|
if (GetCubebContext())
|
|
{
|
|
const cubeb_device_collection& col = input ? s_cubeb_input_devices : s_cubeb_output_devices;
|
|
for (size_t i = 0; i < col.count; i++)
|
|
ret.emplace_back(col.device[i].device_id, col.device[i].friendly_name);
|
|
|
|
ReleaseCubebContext();
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
bool CubebAudioDevice::Start()
|
|
{
|
|
if (mStream)
|
|
Stop();
|
|
|
|
if (!mDeviceName.empty() && mDeviceName != "cubeb_default" && !mDeviceId)
|
|
{
|
|
Console.Error("(audiodev_cubeb) Device '%s' is not available.", mDeviceName.c_str());
|
|
return false;
|
|
}
|
|
|
|
cubeb_stream_params params;
|
|
params.format = CUBEB_SAMPLE_S16LE;
|
|
params.rate = mSampleRate;
|
|
params.channels = mChannels;
|
|
params.layout = CUBEB_LAYOUT_UNDEFINED;
|
|
params.prefs = CUBEB_STREAM_PREF_NONE;
|
|
|
|
// Prefer minimum latency, reduces the chance of dropped samples due to the extra buffer.
|
|
if (cubeb_get_min_latency(mContext, ¶ms, &mStreamLatency) != CUBEB_OK)
|
|
mStreamLatency = (mLatency * mSampleRate) / 1000u;
|
|
|
|
const bool input = (mAudioDir == AUDIODIR_SOURCE);
|
|
int res = cubeb_stream_init(mContext, &mStream, fmt::format("{}", (void*)this).c_str(),
|
|
input ? mDeviceId : nullptr, input ? ¶ms : nullptr, input ? nullptr : mDeviceId,
|
|
input ? nullptr : ¶ms, mStreamLatency,
|
|
&CubebAudioDevice::DataCallback, &CubebStateCallback, this);
|
|
if (res != CUBEB_OK)
|
|
{
|
|
Console.Error("(audiodev_cubeb) cubeb_stream_init() failed: %d", res);
|
|
return false;
|
|
}
|
|
|
|
ResetBuffers();
|
|
|
|
res = cubeb_stream_start(mStream);
|
|
if (res != CUBEB_OK)
|
|
{
|
|
Console.Error("(audiodev_cubeb) cubeb_stream_start() failed: %d", res);
|
|
cubeb_stream_destroy(mStream);
|
|
mStream = nullptr;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CubebAudioDevice::Stop()
|
|
{
|
|
if (!mStream)
|
|
return;
|
|
|
|
int res = cubeb_stream_stop(mStream);
|
|
if (res != CUBEB_OK)
|
|
Console.Error("cubeb_stream_stop() returned %d", res);
|
|
|
|
cubeb_stream_destroy(mStream);
|
|
mStream = nullptr;
|
|
}
|
|
|
|
uint32_t CubebAudioDevice::GetBuffer(short* buff, uint32_t frames)
|
|
{
|
|
if (!mStream)
|
|
return 0;
|
|
|
|
std::lock_guard<std::mutex> lk(mMutex);
|
|
const size_t read_size = frames * sizeof(buff[0]) * GetChannels();
|
|
const size_t bytes_read = mBuffer.read(buff, read_size);
|
|
return (bytes_read / sizeof(buff[0]) / GetChannels());
|
|
}
|
|
|
|
uint32_t CubebAudioDevice::SetBuffer(short* buff, uint32_t frames)
|
|
{
|
|
if (!mStream)
|
|
return frames;
|
|
|
|
std::lock_guard<std::mutex> lk(mMutex);
|
|
size_t nbytes = frames * sizeof(short) * GetChannels();
|
|
mBuffer.write((uint8_t*)buff, nbytes);
|
|
|
|
return frames;
|
|
}
|
|
|
|
bool CubebAudioDevice::GetFrames(uint32_t* size)
|
|
{
|
|
if (!mStream)
|
|
return true;
|
|
|
|
std::lock_guard<std::mutex> lk(mMutex);
|
|
*size = mBuffer.size() / sizeof(short) / GetChannels();
|
|
return true;
|
|
}
|
|
|
|
void CubebAudioDevice::SetResampling(int samplerate)
|
|
{
|
|
const bool was_started = (mStream != nullptr);
|
|
|
|
Stop();
|
|
|
|
mSampleRate = samplerate;
|
|
|
|
if (was_started)
|
|
Start();
|
|
|
|
ResetBuffers();
|
|
}
|
|
|
|
void CubebAudioDevice::ResetBuffers()
|
|
{
|
|
std::lock_guard<std::mutex> lk(mMutex);
|
|
const u32 samples = std::max(((mSampleRate * mChannels) * mLatency) / 1000u, mStreamLatency * mChannels);
|
|
mBuffer.reset(sizeof(u16) * samples);
|
|
}
|
|
|
|
long CubebAudioDevice::DataCallback(
|
|
cubeb_stream* stream, void* user_ptr, void const* input_buffer, void* output_buffer, long nframes)
|
|
{
|
|
CubebAudioDevice* const ad = static_cast<CubebAudioDevice*>(user_ptr);
|
|
const size_t bytes = ad->mChannels * sizeof(short) * static_cast<size_t>(nframes);
|
|
|
|
std::lock_guard<std::mutex> lk(ad->mMutex);
|
|
if (ad->mAudioDir == AUDIODIR_SOURCE)
|
|
{
|
|
ad->mBuffer.write(input_buffer, bytes);
|
|
}
|
|
else
|
|
{
|
|
const size_t written = ad->mBuffer.read(output_buffer, bytes);
|
|
if (written < bytes)
|
|
std::memset(static_cast<u8*>(output_buffer) + written, 0, bytes - written);
|
|
}
|
|
|
|
return nframes;
|
|
}
|
|
} // namespace audiodev_cubeb
|
|
} // namespace usb_mic
|