Implement proper microphone support (#251)

This commit is contained in:
Adrian Graber 2022-11-03 00:24:34 +01:00 committed by GitHub
parent dfa7774c4c
commit d4e14d2b05
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 682 additions and 36 deletions

View file

@ -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()

View file

@ -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
View 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
View 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);
};

View 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));
}
}

View 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;