mirror of
https://github.com/cemu-project/Cemu.git
synced 2025-07-07 07:21:18 +12:00
Implement proper microphone support (#251)
This commit is contained in:
parent
dfa7774c4c
commit
d4e14d2b05
13 changed files with 682 additions and 36 deletions
|
@ -1,6 +1,8 @@
|
|||
add_library(CemuAudio
|
||||
IAudioAPI.cpp
|
||||
IAudioAPI.h
|
||||
IAudioInputAPI.cpp
|
||||
IAudioInputAPI.h
|
||||
)
|
||||
|
||||
set_property(TARGET CemuAudio PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
|
||||
|
@ -26,6 +28,8 @@ if(ENABLE_CUBEB)
|
|||
target_sources(CemuAudio PRIVATE
|
||||
CubebAPI.cpp
|
||||
CubebAPI.h
|
||||
CubebInputAPI.cpp
|
||||
CubebInputAPI.h
|
||||
)
|
||||
#add_compile_definitions(HAS_CUBEB)
|
||||
endif()
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
#endif
|
||||
|
||||
|
||||
void state_cb(cubeb_stream* stream, void* user, cubeb_state state)
|
||||
static void state_cb(cubeb_stream* stream, void* user, cubeb_state state)
|
||||
{
|
||||
if (!stream)
|
||||
return;
|
||||
|
|
216
src/audio/CubebInputAPI.cpp
Normal file
216
src/audio/CubebInputAPI.cpp
Normal file
|
@ -0,0 +1,216 @@
|
|||
#include "CubebInputAPI.h"
|
||||
|
||||
#if BOOST_OS_WINDOWS
|
||||
#include <combaseapi.h>
|
||||
#include <mmreg.h>
|
||||
#include <mmsystem.h>
|
||||
#pragma comment(lib, "Avrt.lib")
|
||||
#pragma comment(lib, "ksuser.lib")
|
||||
#endif
|
||||
|
||||
static void state_cb(cubeb_stream* stream, void* user, cubeb_state state)
|
||||
{
|
||||
if (!stream)
|
||||
return;
|
||||
|
||||
/*switch (state)
|
||||
{
|
||||
case CUBEB_STATE_STARTED:
|
||||
fprintf(stderr, "stream started\n");
|
||||
break;
|
||||
case CUBEB_STATE_STOPPED:
|
||||
fprintf(stderr, "stream stopped\n");
|
||||
break;
|
||||
case CUBEB_STATE_DRAINED:
|
||||
fprintf(stderr, "stream drained\n");
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "unknown stream state %d\n", state);
|
||||
}*/
|
||||
}
|
||||
|
||||
long CubebInputAPI::data_cb(cubeb_stream* stream, void* user, const void* inputbuffer, void* outputbuffer, long nframes)
|
||||
{
|
||||
auto* thisptr = (CubebInputAPI*)user;
|
||||
|
||||
const auto size = (size_t)nframes * thisptr->m_channels * (thisptr->m_bitsPerSample / 8);
|
||||
|
||||
std::unique_lock lock(thisptr->m_mutex);
|
||||
if (thisptr->m_buffer.capacity() <= thisptr->m_buffer.size() + size)
|
||||
{
|
||||
forceLogDebug_printf("dropped input sound block since too many buffers are queued");
|
||||
return nframes;
|
||||
}
|
||||
|
||||
thisptr->m_buffer.insert(thisptr->m_buffer.end(), (uint8*)inputbuffer, (uint8*)inputbuffer + size);
|
||||
|
||||
return nframes;
|
||||
}
|
||||
|
||||
CubebInputAPI::CubebInputAPI(cubeb_devid devid, uint32 samplerate, uint32 channels, uint32 samples_per_block,
|
||||
uint32 bits_per_sample)
|
||||
: IAudioInputAPI(samplerate, channels, samples_per_block, bits_per_sample)
|
||||
{
|
||||
cubeb_stream_params input_params;
|
||||
|
||||
input_params.format = CUBEB_SAMPLE_S16LE;
|
||||
input_params.rate = samplerate;
|
||||
input_params.channels = channels;
|
||||
input_params.prefs = CUBEB_STREAM_PREF_NONE;
|
||||
|
||||
switch (channels)
|
||||
{
|
||||
case 8:
|
||||
input_params.layout = CUBEB_LAYOUT_3F4_LFE;
|
||||
break;
|
||||
case 6:
|
||||
input_params.layout = CUBEB_LAYOUT_QUAD_LFE | CHANNEL_FRONT_CENTER;
|
||||
break;
|
||||
case 4:
|
||||
input_params.layout = CUBEB_LAYOUT_QUAD;
|
||||
break;
|
||||
case 2:
|
||||
input_params.layout = CUBEB_LAYOUT_STEREO;
|
||||
break;
|
||||
default:
|
||||
input_params.layout = CUBEB_LAYOUT_MONO;
|
||||
break;
|
||||
}
|
||||
|
||||
uint32 latency = 1;
|
||||
cubeb_get_min_latency(s_context, &input_params, &latency);
|
||||
|
||||
m_buffer.reserve((size_t)m_bytesPerBlock * kBlockCount);
|
||||
|
||||
if (cubeb_stream_init(s_context, &m_stream, "Cemu Cubeb input",
|
||||
devid, &input_params,
|
||||
nullptr, nullptr,
|
||||
latency, data_cb, state_cb, this) != CUBEB_OK)
|
||||
{
|
||||
throw std::runtime_error("can't initialize cubeb device");
|
||||
}
|
||||
}
|
||||
|
||||
CubebInputAPI::~CubebInputAPI()
|
||||
{
|
||||
if (m_stream)
|
||||
{
|
||||
Stop();
|
||||
cubeb_stream_destroy(m_stream);
|
||||
}
|
||||
}
|
||||
|
||||
bool CubebInputAPI::ConsumeBlock(sint16* data)
|
||||
{
|
||||
std::unique_lock lock(m_mutex);
|
||||
if (m_buffer.empty())
|
||||
{
|
||||
// we got no data, just write silence
|
||||
memset(data, 0x00, m_bytesPerBlock);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto copied = std::min(m_buffer.size(), (size_t)m_bytesPerBlock);
|
||||
memcpy(data, m_buffer.data(), copied);
|
||||
m_buffer.erase(m_buffer.begin(), std::next(m_buffer.begin(), copied));
|
||||
lock.unlock();
|
||||
// fill rest with silence
|
||||
if (copied != m_bytesPerBlock)
|
||||
memset((uint8*)data + copied, 0x00, m_bytesPerBlock - copied);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CubebInputAPI::Play()
|
||||
{
|
||||
if (m_is_playing)
|
||||
return true;
|
||||
|
||||
if (cubeb_stream_start(m_stream) == CUBEB_OK)
|
||||
{
|
||||
m_is_playing = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CubebInputAPI::Stop()
|
||||
{
|
||||
if (!m_is_playing)
|
||||
return true;
|
||||
|
||||
if (cubeb_stream_stop(m_stream) == CUBEB_OK)
|
||||
{
|
||||
m_is_playing = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void CubebInputAPI::SetVolume(sint32 volume)
|
||||
{
|
||||
IAudioInputAPI::SetVolume(volume);
|
||||
cubeb_stream_set_volume(m_stream, (float)volume / 100.0f);
|
||||
}
|
||||
|
||||
bool CubebInputAPI::InitializeStatic()
|
||||
{
|
||||
#if BOOST_OS_WINDOWS
|
||||
s_com_initialized = (SUCCEEDED(CoInitializeEx(nullptr, COINIT_MULTITHREADED)));
|
||||
#endif
|
||||
|
||||
if (cubeb_init(&s_context, "Cemu Input Cubeb", nullptr))
|
||||
{
|
||||
cemuLog_force("can't create cubeb audio api");
|
||||
|
||||
#if BOOST_OS_WINDOWS
|
||||
if (s_com_initialized)
|
||||
{
|
||||
CoUninitialize();
|
||||
s_com_initialized = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CubebInputAPI::Destroy()
|
||||
{
|
||||
if (s_context)
|
||||
cubeb_destroy(s_context);
|
||||
#if BOOST_OS_WINDOWS
|
||||
if (s_com_initialized)
|
||||
CoUninitialize();
|
||||
#endif
|
||||
}
|
||||
|
||||
std::vector<IAudioInputAPI::DeviceDescriptionPtr> CubebInputAPI::GetDevices()
|
||||
{
|
||||
cubeb_device_collection devices;
|
||||
if (cubeb_enumerate_devices(s_context, CUBEB_DEVICE_TYPE_INPUT, &devices) != CUBEB_OK)
|
||||
return {};
|
||||
|
||||
std::vector<DeviceDescriptionPtr> result;
|
||||
result.reserve(devices.count);
|
||||
for (size_t i = 0; i < devices.count; ++i)
|
||||
{
|
||||
//const auto& device = devices.device[i];
|
||||
if (devices.device[i].state == CUBEB_DEVICE_STATE_ENABLED)
|
||||
{
|
||||
auto device = std::make_shared<CubebDeviceDescription>(devices.device[i].devid, devices.device[i].device_id,
|
||||
boost::nowide::widen(
|
||||
devices.device[i].friendly_name));
|
||||
result.emplace_back(device);
|
||||
}
|
||||
}
|
||||
|
||||
cubeb_device_collection_destroy(s_context, &devices);
|
||||
|
||||
return result;
|
||||
}
|
52
src/audio/CubebInputAPI.h
Normal file
52
src/audio/CubebInputAPI.h
Normal file
|
@ -0,0 +1,52 @@
|
|||
#pragma once
|
||||
|
||||
#include "IAudioInputAPI.h"
|
||||
|
||||
#include <cubeb/cubeb.h>
|
||||
|
||||
class CubebInputAPI : public IAudioInputAPI
|
||||
{
|
||||
public:
|
||||
class CubebDeviceDescription : public DeviceDescription
|
||||
{
|
||||
public:
|
||||
CubebDeviceDescription(cubeb_devid devid, std::string device_id, const std::wstring& name)
|
||||
: DeviceDescription(name), m_devid(devid), m_device_id(std::move(device_id)) { }
|
||||
|
||||
std::wstring GetIdentifier() const override { return boost::nowide::widen(m_device_id); }
|
||||
cubeb_devid GetDeviceId() const { return m_devid; }
|
||||
|
||||
private:
|
||||
cubeb_devid m_devid;
|
||||
std::string m_device_id;
|
||||
};
|
||||
|
||||
using CubebDeviceDescriptionPtr = std::shared_ptr<CubebDeviceDescription>;
|
||||
|
||||
CubebInputAPI(cubeb_devid devid, uint32 samplerate, uint32 channels, uint32 samples_per_block, uint32 bits_per_sample);
|
||||
~CubebInputAPI();
|
||||
|
||||
AudioInputAPI GetType() const override { return Cubeb; }
|
||||
|
||||
bool ConsumeBlock(sint16* data) override;
|
||||
bool Play() override;
|
||||
bool Stop() override;
|
||||
bool IsPlaying() const override { return m_is_playing; };
|
||||
void SetVolume(sint32 volume) override;
|
||||
|
||||
static std::vector<DeviceDescriptionPtr> GetDevices();
|
||||
|
||||
static bool InitializeStatic();
|
||||
static void Destroy();
|
||||
|
||||
private:
|
||||
inline static bool s_com_initialized = false;
|
||||
inline static cubeb* s_context = nullptr;
|
||||
|
||||
cubeb_stream* m_stream = nullptr;
|
||||
bool m_is_playing = false;
|
||||
|
||||
mutable std::shared_mutex m_mutex;
|
||||
std::vector<uint8> m_buffer;
|
||||
static long data_cb(cubeb_stream* stream, void* user, const void* inputbuffer, void* outputbuffer, long nframes);
|
||||
};
|
66
src/audio/IAudioInputAPI.cpp
Normal file
66
src/audio/IAudioInputAPI.cpp
Normal file
|
@ -0,0 +1,66 @@
|
|||
#include "IAudioInputAPI.h"
|
||||
#include "CubebInputAPI.h"
|
||||
|
||||
std::shared_mutex g_audioInputMutex;
|
||||
AudioInputAPIPtr g_inputAudio;
|
||||
|
||||
std::array<bool, IAudioInputAPI::AudioInputAPIEnd> IAudioInputAPI::s_availableApis{};
|
||||
|
||||
IAudioInputAPI::IAudioInputAPI(uint32 samplerate, uint32 channels, uint32 samples_per_block, uint32 bits_per_sample)
|
||||
: m_samplerate(samplerate), m_channels(channels), m_samplesPerBlock(samples_per_block), m_bitsPerSample(bits_per_sample)
|
||||
{
|
||||
m_bytesPerBlock = samples_per_block * channels * (bits_per_sample / 8);
|
||||
}
|
||||
|
||||
void IAudioInputAPI::PrintLogging()
|
||||
{
|
||||
forceLog_printf("------- Init Audio Input backend -------");
|
||||
forceLog_printf("Cubeb: %s", s_availableApis[Cubeb] ? "available" : "not supported");
|
||||
}
|
||||
|
||||
void IAudioInputAPI::InitializeStatic()
|
||||
{
|
||||
s_availableApis[Cubeb] = CubebInputAPI::InitializeStatic();
|
||||
}
|
||||
|
||||
bool IAudioInputAPI::IsAudioInputAPIAvailable(AudioInputAPI api)
|
||||
{
|
||||
if ((size_t)api < s_availableApis.size())
|
||||
return s_availableApis[api];
|
||||
|
||||
cemu_assert_debug(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
AudioInputAPIPtr IAudioInputAPI::CreateDevice(AudioInputAPI api, const DeviceDescriptionPtr& device, sint32 samplerate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample)
|
||||
{
|
||||
if (!IsAudioInputAPIAvailable(api))
|
||||
return {};
|
||||
|
||||
switch(api)
|
||||
{
|
||||
case Cubeb:
|
||||
{
|
||||
const auto tmp = std::dynamic_pointer_cast<CubebInputAPI::CubebDeviceDescription>(device);
|
||||
return std::make_unique<CubebInputAPI>(tmp->GetDeviceId(), samplerate, channels, samples_per_block, bits_per_sample);
|
||||
}
|
||||
default:
|
||||
throw std::runtime_error(fmt::format("invalid audio api: {}", api));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<IAudioInputAPI::DeviceDescriptionPtr> IAudioInputAPI::GetDevices(AudioInputAPI api)
|
||||
{
|
||||
if (!IsAudioInputAPIAvailable(api))
|
||||
return {};
|
||||
|
||||
switch(api)
|
||||
{
|
||||
case Cubeb:
|
||||
{
|
||||
return CubebInputAPI::GetDevices();
|
||||
}
|
||||
default:
|
||||
throw std::runtime_error(fmt::format("invalid audio api: {}", api));
|
||||
}
|
||||
}
|
71
src/audio/IAudioInputAPI.h
Normal file
71
src/audio/IAudioInputAPI.h
Normal file
|
@ -0,0 +1,71 @@
|
|||
#pragma once
|
||||
|
||||
class IAudioInputAPI
|
||||
{
|
||||
friend class GeneralSettings2;
|
||||
|
||||
public:
|
||||
class DeviceDescription
|
||||
{
|
||||
public:
|
||||
explicit DeviceDescription(std::wstring name)
|
||||
: m_name(std::move(name)) { }
|
||||
|
||||
virtual ~DeviceDescription() = default;
|
||||
virtual std::wstring GetIdentifier() const = 0;
|
||||
const std::wstring& GetName() const { return m_name; }
|
||||
|
||||
bool operator==(const DeviceDescription& o) const
|
||||
{
|
||||
return GetIdentifier() == o.GetIdentifier();
|
||||
}
|
||||
|
||||
private:
|
||||
std::wstring m_name;
|
||||
};
|
||||
|
||||
using DeviceDescriptionPtr = std::shared_ptr<DeviceDescription>;
|
||||
|
||||
enum AudioInputAPI
|
||||
{
|
||||
Cubeb,
|
||||
|
||||
AudioInputAPIEnd,
|
||||
};
|
||||
static constexpr uint32 kBlockCount = 24;
|
||||
|
||||
IAudioInputAPI(uint32 samplerate, uint32 channels, uint32 samples_per_block, uint32 bits_per_sample);
|
||||
virtual ~IAudioInputAPI() = default;
|
||||
virtual AudioInputAPI GetType() const = 0;
|
||||
|
||||
sint32 GetChannels() const { return m_channels; }
|
||||
|
||||
virtual sint32 GetVolume() const { return m_volume; }
|
||||
virtual void SetVolume(sint32 volume) { m_volume = volume; }
|
||||
|
||||
virtual bool ConsumeBlock(sint16* data) = 0;
|
||||
virtual bool Play() = 0;
|
||||
virtual bool Stop() = 0;
|
||||
virtual bool IsPlaying() const = 0;
|
||||
|
||||
static void PrintLogging();
|
||||
static void InitializeStatic();
|
||||
static bool IsAudioInputAPIAvailable(AudioInputAPI api);
|
||||
|
||||
static std::unique_ptr<IAudioInputAPI> CreateDevice(AudioInputAPI api, const DeviceDescriptionPtr& device, sint32 samplerate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample);
|
||||
static std::vector<DeviceDescriptionPtr> GetDevices(AudioInputAPI api);
|
||||
|
||||
protected:
|
||||
uint32 m_samplerate, m_channels, m_samplesPerBlock, m_bitsPerSample;
|
||||
uint32 m_bytesPerBlock;
|
||||
|
||||
sint32 m_volume = 0;
|
||||
|
||||
static std::array<bool, AudioInputAPIEnd> s_availableApis;
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
using AudioInputAPIPtr = std::unique_ptr<IAudioInputAPI>;
|
||||
extern std::shared_mutex g_audioInputMutex;
|
||||
extern AudioInputAPIPtr g_inputAudio;
|
Loading…
Add table
Add a link
Reference in a new issue