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

@ -5,6 +5,7 @@
#include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h" #include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h"
#include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h" #include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h"
#include "audio/IAudioAPI.h" #include "audio/IAudioAPI.h"
#include "audio/IAudioInputAPI.h"
#include "Cafe/HW/Espresso/Debugger/Debugger.h" #include "Cafe/HW/Espresso/Debugger/Debugger.h"
#include "config/ActiveSettings.h" #include "config/ActiveSettings.h"
@ -402,6 +403,7 @@ void cemu_initForGame()
GraphicPack2::ActivateForCurrentTitle(); GraphicPack2::ActivateForCurrentTitle();
// print audio log // print audio log
IAudioAPI::PrintLogging(); IAudioAPI::PrintLogging();
IAudioInputAPI::PrintLogging();
// everything initialized // everything initialized
forceLog_printf("------- Run title -------"); forceLog_printf("------- Run title -------");
// wait till GPU thread is initialized // wait till GPU thread is initialized

View File

@ -1,5 +1,7 @@
#include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/common/OSCommon.h"
#include "input/InputManager.h" #include "input/InputManager.h"
#include "audio/IAudioInputAPI.h"
#include "config/CemuConfig.h"
enum class MIC_RESULT enum class MIC_RESULT
{ {
@ -13,6 +15,7 @@ enum class MIC_RESULT
#define MIC_SAMPLERATE 32000 #define MIC_SAMPLERATE 32000
const int MIC_SAMPLES_PER_3MS_32KHZ = (96); // 32000*3/1000
enum class MIC_STATUS_FLAGS : uint32 enum class MIC_STATUS_FLAGS : uint32
{ {
@ -22,8 +25,8 @@ enum class MIC_STATUS_FLAGS : uint32
}; };
DEFINE_ENUM_FLAG_OPERATORS(MIC_STATUS_FLAGS); DEFINE_ENUM_FLAG_OPERATORS(MIC_STATUS_FLAGS);
#define MIC_HANDLE_DRC0 100 #define MIC_HANDLE_DRC0 0
#define MIC_HANDLE_DRC1 101 #define MIC_HANDLE_DRC1 1
enum class MIC_STATEID enum class MIC_STATEID
{ {
@ -139,6 +142,36 @@ void micExport_MICInit(PPCInterpreter_t* hCPU)
// return status // return status
memory_writeU32(hCPU->gpr[6], 0); // no error memory_writeU32(hCPU->gpr[6], 0); // no error
osLib_returnFromFunction(hCPU, (drcIndex==0)?MIC_HANDLE_DRC0:MIC_HANDLE_DRC1); // success osLib_returnFromFunction(hCPU, (drcIndex==0)?MIC_HANDLE_DRC0:MIC_HANDLE_DRC1); // success
auto& config = GetConfig();
const auto audio_api = IAudioInputAPI::Cubeb; // change this if more input apis get implemented
std::unique_lock lock(g_audioInputMutex);
if (!g_inputAudio)
{
IAudioInputAPI::DeviceDescriptionPtr device_description;
if (IAudioInputAPI::IsAudioInputAPIAvailable(audio_api))
{
auto devices = IAudioInputAPI::GetDevices(audio_api);
const auto it = std::find_if(devices.begin(), devices.end(), [&config](const auto& d) {return d->GetIdentifier() == config.input_device; });
if (it != devices.end())
device_description = *it;
}
if (device_description)
{
try
{
g_inputAudio = IAudioInputAPI::CreateDevice(audio_api, device_description, MIC_SAMPLERATE, 1, MIC_SAMPLES_PER_3MS_32KHZ, 16);
g_inputAudio->SetVolume(config.input_volume);
}
catch (std::runtime_error& ex)
{
forceLog_printf("can't initialize audio input: %s", ex.what());
exit(0);
}
}
}
} }
void micExport_MICOpen(PPCInterpreter_t* hCPU) void micExport_MICOpen(PPCInterpreter_t* hCPU)
@ -172,6 +205,10 @@ void micExport_MICOpen(PPCInterpreter_t* hCPU)
// success // success
MICStatus.drc[drcIndex].isOpen = true; MICStatus.drc[drcIndex].isOpen = true;
osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::SUCCESS); osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::SUCCESS);
std::shared_lock lock(g_audioInputMutex);
if(g_inputAudio)
g_inputAudio->Play();
} }
void micExport_MICClose(PPCInterpreter_t* hCPU) void micExport_MICClose(PPCInterpreter_t* hCPU)
@ -198,6 +235,10 @@ void micExport_MICClose(PPCInterpreter_t* hCPU)
// success // success
MICStatus.drc[drcIndex].isOpen = false; MICStatus.drc[drcIndex].isOpen = false;
osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::SUCCESS); osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::SUCCESS);
std::shared_lock lock(g_audioInputMutex);
if (g_inputAudio)
g_inputAudio->Stop();
} }
void micExport_MICGetStatus(PPCInterpreter_t* hCPU) void micExport_MICGetStatus(PPCInterpreter_t* hCPU)
@ -360,6 +401,17 @@ void micExport_MICSetDataConsumed(PPCInterpreter_t* hCPU)
return; return;
} }
void mic_updateDevicePlayState(bool isPlaying)
{
if (g_inputAudio)
{
if (isPlaying)
g_inputAudio->Play();
else
g_inputAudio->Stop();
}
}
void mic_updateOnAXFrame() void mic_updateOnAXFrame()
{ {
sint32 drcIndex = 0; sint32 drcIndex = 0;
@ -368,11 +420,23 @@ void mic_updateOnAXFrame()
drcIndex = 1; drcIndex = 1;
if (mic_isActive(1) == false) if (mic_isActive(1) == false)
{ {
std::shared_lock lock(g_audioInputMutex);
mic_updateDevicePlayState(false);
return; return;
} }
} }
const sint32 micSampleCount = 32000/32; std::shared_lock lock(g_audioInputMutex);
mic_updateDevicePlayState(true);
if (g_inputAudio)
{
sint16 micSampleData[MIC_SAMPLES_PER_3MS_32KHZ];
g_inputAudio->ConsumeBlock(micSampleData);
mic_feedSamples(0, micSampleData, MIC_SAMPLES_PER_3MS_32KHZ);
}
else
{
const sint32 micSampleCount = 32000 / 32;
sint16 micSampleData[micSampleCount]; sint16 micSampleData[micSampleCount];
auto controller = InputManager::instance().get_vpad_controller(drcIndex); auto controller = InputManager::instance().get_vpad_controller(drcIndex);
@ -388,6 +452,7 @@ void mic_updateOnAXFrame()
memset(micSampleData, 0x00, sizeof(micSampleData)); memset(micSampleData, 0x00, sizeof(micSampleData));
} }
mic_feedSamples(0, micSampleData, micSampleCount); mic_feedSamples(0, micSampleData, micSampleCount);
}
} }
namespace mic namespace mic

View File

@ -1,6 +1,8 @@
add_library(CemuAudio add_library(CemuAudio
IAudioAPI.cpp IAudioAPI.cpp
IAudioAPI.h IAudioAPI.h
IAudioInputAPI.cpp
IAudioInputAPI.h
) )
set_property(TARGET CemuAudio PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>") set_property(TARGET CemuAudio PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
@ -26,6 +28,8 @@ if(ENABLE_CUBEB)
target_sources(CemuAudio PRIVATE target_sources(CemuAudio PRIVATE
CubebAPI.cpp CubebAPI.cpp
CubebAPI.h CubebAPI.h
CubebInputAPI.cpp
CubebInputAPI.h
) )
#add_compile_definitions(HAS_CUBEB) #add_compile_definitions(HAS_CUBEB)
endif() endif()

View File

@ -9,7 +9,7 @@
#endif #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) if (!stream)
return; 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;

View File

@ -295,8 +295,10 @@ void CemuConfig::Load(XMLConfigParser& parser)
audio_delay = audio.get("delay", 2); audio_delay = audio.get("delay", 2);
tv_channels = audio.get("TVChannels", kStereo); tv_channels = audio.get("TVChannels", kStereo);
pad_channels = audio.get("PadChannels", kStereo); pad_channels = audio.get("PadChannels", kStereo);
input_channels = audio.get("InputChannels", kMono);
tv_volume = audio.get("TVVolume", 20); tv_volume = audio.get("TVVolume", 20);
pad_volume = audio.get("PadVolume", 0); pad_volume = audio.get("PadVolume", 0);
input_volume = audio.get("InputVolume", 20);
const auto tv = audio.get("TVDevice", ""); const auto tv = audio.get("TVDevice", "");
try try
@ -318,6 +320,16 @@ void CemuConfig::Load(XMLConfigParser& parser)
forceLog_printf("config load error: can't load pad device: %s", pad); forceLog_printf("config load error: can't load pad device: %s", pad);
} }
const auto input_device_name = audio.get("InputDevice", "");
try
{
input_device = boost::nowide::widen(input_device_name);
}
catch (const std::exception&)
{
forceLog_printf("config load error: can't load input device: %s", input_device_name);
}
// account // account
auto acc = parser.get("Account"); auto acc = parser.get("Account");
account.m_persistent_id = acc.get("PersistentId", account.m_persistent_id); account.m_persistent_id = acc.get("PersistentId", account.m_persistent_id);
@ -488,10 +500,13 @@ void CemuConfig::Save(XMLConfigParser& parser)
audio.set("delay", audio_delay); audio.set("delay", audio_delay);
audio.set("TVChannels", tv_channels); audio.set("TVChannels", tv_channels);
audio.set("PadChannels", pad_channels); audio.set("PadChannels", pad_channels);
audio.set("InputChannels", input_channels);
audio.set("TVVolume", tv_volume); audio.set("TVVolume", tv_volume);
audio.set("PadVolume", pad_volume); audio.set("PadVolume", pad_volume);
audio.set("InputVolume", input_volume);
audio.set("TVDevice", boost::nowide::narrow(tv_device).c_str()); audio.set("TVDevice", boost::nowide::narrow(tv_device).c_str());
audio.set("PadDevice", boost::nowide::narrow(pad_device).c_str()); audio.set("PadDevice", boost::nowide::narrow(pad_device).c_str());
audio.set("InputDevice", boost::nowide::narrow(input_device).c_str());
// account // account
auto acc = config.set("Account"); auto acc = config.set("Account");

View File

@ -462,9 +462,9 @@ struct CemuConfig
// audio // audio
sint32 audio_api = 0; sint32 audio_api = 0;
sint32 audio_delay = 2; sint32 audio_delay = 2;
AudioChannels tv_channels = kStereo, pad_channels = kStereo; AudioChannels tv_channels = kStereo, pad_channels = kStereo, input_channels = kMono;
sint32 tv_volume = 50, pad_volume = 0; sint32 tv_volume = 50, pad_volume = 0, input_volume = 50;
std::wstring tv_device{ L"default" }, pad_device; std::wstring tv_device{ L"default" }, pad_device, input_device;
// account // account
struct struct

View File

@ -23,6 +23,8 @@
#endif #endif
#include "audio/CubebAPI.h" #include "audio/CubebAPI.h"
#include "audio/IAudioInputAPI.h"
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h"
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h"
#include "Cafe/Account/Account.h" #include "Cafe/Account/Account.h"
@ -70,6 +72,15 @@ private:
IAudioAPI::DeviceDescriptionPtr m_description; IAudioAPI::DeviceDescriptionPtr m_description;
}; };
class wxInputDeviceDescription : public wxClientData
{
public:
wxInputDeviceDescription(const IAudioInputAPI::DeviceDescriptionPtr& description) : m_description(description) {}
const IAudioInputAPI::DeviceDescriptionPtr& GetDescription() const { return m_description; }
private:
IAudioInputAPI::DeviceDescriptionPtr m_description;
};
class wxVulkanUUID : public wxClientData class wxVulkanUUID : public wxClientData
{ {
public: public:
@ -424,6 +435,47 @@ wxPanel* GeneralSettings2::AddAudioPage(wxNotebook* notebook)
audio_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); audio_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5);
} }
{
auto box = new wxStaticBox(audio_panel, wxID_ANY, _("Microphone"));
auto box_sizer = new wxStaticBoxSizer(box, wxVERTICAL);
auto audio_input_row = new wxFlexGridSizer(0, 3, 0, 0);
audio_input_row->SetFlexibleDirection(wxBOTH);
audio_input_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
audio_input_row->Add(new wxStaticText(box, wxID_ANY, _("Device")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
m_input_device = new wxChoice(box, wxID_ANY, wxDefaultPosition);
m_input_device->SetMinSize(wxSize(300, -1));
m_input_device->SetToolTip(_("Select the active audio input device for Wii U GamePad"));
audio_input_row->Add(m_input_device, 0, wxEXPAND | wxALL, 5);
audio_input_row->AddSpacer(0);
m_input_device->Bind(wxEVT_CHOICE, &GeneralSettings2::OnAudioDeviceSelected, this);
const wxString audio_channel_drc_choices[] = { _("Mono") }; // mono for now only
audio_input_row->Add(new wxStaticText(box, wxID_ANY, _("Channels")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
m_input_channels = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(audio_channel_drc_choices), audio_channel_drc_choices);
m_input_channels->SetSelection(0); // set default to stereo
m_input_channels->Bind(wxEVT_CHOICE, &GeneralSettings2::OnAudioChannelsSelected, this);
audio_input_row->Add(m_input_channels, 0, wxEXPAND | wxALL, 5);
audio_input_row->AddSpacer(0);
audio_input_row->Add(new wxStaticText(box, wxID_ANY, _("Volume")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
m_input_volume = new wxSlider(box, wxID_ANY, 100, 0, 100);
audio_input_row->Add(m_input_volume, 0, wxEXPAND | wxALL, 5);
auto audio_input_volume_text = new wxStaticText(box, wxID_ANY, wxT("100%"));
audio_input_row->Add(audio_input_volume_text, 0, wxALIGN_CENTER_VERTICAL | wxALL | wxALIGN_RIGHT, 5);
m_input_volume->Bind(wxEVT_SLIDER, &GeneralSettings2::OnSliderChangedPercent, this, wxID_ANY, wxID_ANY, new wxControlObject(audio_input_volume_text));
m_input_volume->Bind(wxEVT_SLIDER, &GeneralSettings2::OnVolumeChanged, this);
box_sizer->Add(audio_input_row, 1, wxEXPAND, 5);
audio_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5);
}
audio_panel->SetSizerAndFit(audio_panel_sizer); audio_panel->SetSizerAndFit(audio_panel_sizer);
return audio_panel; return audio_panel;
} }
@ -857,9 +909,12 @@ void GeneralSettings2::StoreConfig()
config.tv_channels = (AudioChannels)m_tv_channels->GetSelection(); config.tv_channels = (AudioChannels)m_tv_channels->GetSelection();
//config.pad_channels = (AudioChannels)m_pad_channels->GetSelection(); //config.pad_channels = (AudioChannels)m_pad_channels->GetSelection();
config.pad_channels = kStereo; // (AudioChannels)m_pad_channels->GetSelection(); config.pad_channels = kStereo; // (AudioChannels)m_pad_channels->GetSelection();
//config.input_channels = (AudioChannels)m_input_channels->GetSelection();
config.input_channels = kMono; // (AudioChannels)m_input_channels->GetSelection();
config.tv_volume = m_tv_volume->GetValue(); config.tv_volume = m_tv_volume->GetValue();
config.pad_volume = m_pad_volume->GetValue(); config.pad_volume = m_pad_volume->GetValue();
config.input_volume = m_input_volume->GetValue();
config.tv_device.clear(); config.tv_device.clear();
const auto tv_device = m_tv_device->GetSelection(); const auto tv_device = m_tv_device->GetSelection();
@ -879,6 +934,15 @@ void GeneralSettings2::StoreConfig()
config.pad_device = device_description->GetDescription()->GetIdentifier(); config.pad_device = device_description->GetDescription()->GetIdentifier();
} }
config.input_device = L"";
const auto input_device = m_input_device->GetSelection();
if (input_device != wxNOT_FOUND && input_device != 0 && m_input_device->HasClientObjectData())
{
const auto* device_description = (wxDeviceDescription*)m_input_device->GetClientObject(input_device);
if (device_description)
config.input_device = device_description->GetDescription()->GetIdentifier();
}
// graphics // graphics
config.graphic_api = (GraphicAPI)m_graphic_api->GetSelection(); config.graphic_api = (GraphicAPI)m_graphic_api->GetSelection();
@ -977,6 +1041,15 @@ void GeneralSettings2::OnAudioLatencyChanged(wxCommandEvent& event)
void GeneralSettings2::OnVolumeChanged(wxCommandEvent& event) void GeneralSettings2::OnVolumeChanged(wxCommandEvent& event)
{ {
if(event.GetEventObject() == m_input_volume)
{
std::shared_lock lock(g_audioInputMutex);
if (g_inputAudio)
g_inputAudio->SetVolume(event.GetInt());
}
else
{
std::shared_lock lock(g_audioMutex); std::shared_lock lock(g_audioMutex);
if(event.GetEventObject() == m_pad_volume) if(event.GetEventObject() == m_pad_volume)
{ {
@ -991,6 +1064,7 @@ void GeneralSettings2::OnVolumeChanged(wxCommandEvent& event)
if (g_tvAudio) if (g_tvAudio)
g_tvAudio->SetVolume(event.GetInt()); g_tvAudio->SetVolume(event.GetInt());
} }
}
event.Skip(); event.Skip();
@ -1048,9 +1122,11 @@ void GeneralSettings2::UpdateAudioDeviceList()
{ {
m_tv_device->Clear(); m_tv_device->Clear();
m_pad_device->Clear(); m_pad_device->Clear();
m_input_device->Clear();
m_tv_device->Append(_("Disabled")); m_tv_device->Append(_("Disabled"));
m_pad_device->Append(_("Disabled")); m_pad_device->Append(_("Disabled"));
m_input_device->Append(_("Disabled"));
const auto audio_api = (IAudioAPI::AudioAPI)GetConfig().audio_api; const auto audio_api = (IAudioAPI::AudioAPI)GetConfig().audio_api;
const auto devices = IAudioAPI::GetDevices(audio_api); const auto devices = IAudioAPI::GetDevices(audio_api);
@ -1060,6 +1136,14 @@ void GeneralSettings2::UpdateAudioDeviceList()
m_pad_device->Append(device->GetName(), new wxDeviceDescription(device)); m_pad_device->Append(device->GetName(), new wxDeviceDescription(device));
} }
const auto input_audio_api = IAudioInputAPI::Cubeb; //(IAudioAPI::AudioAPI)GetConfig().input_audio_api;
const auto input_devices = IAudioInputAPI::GetDevices(input_audio_api);
for (auto& device : input_devices)
{
m_input_device->Append(device->GetName(), new wxInputDeviceDescription(device));
}
if(m_tv_device->GetCount() > 1) if(m_tv_device->GetCount() > 1)
m_tv_device->SetSelection(1); m_tv_device->SetSelection(1);
else else
@ -1067,6 +1151,8 @@ void GeneralSettings2::UpdateAudioDeviceList()
m_pad_device->SetSelection(0); m_pad_device->SetSelection(0);
m_input_device->SetSelection(0);
// todo reset global instance of audio device // todo reset global instance of audio device
} }
@ -1453,6 +1539,8 @@ void GeneralSettings2::ApplyConfig()
m_tv_channels->SetSelection(config.tv_channels); m_tv_channels->SetSelection(config.tv_channels);
//m_pad_channels->SetSelection(config.pad_channels); //m_pad_channels->SetSelection(config.pad_channels);
m_pad_channels->SetSelection(0); m_pad_channels->SetSelection(0);
//m_input_channels->SetSelection(config.pad_channels);
m_input_channels->SetSelection(0);
SendSliderEvent(m_tv_volume, config.tv_volume); SendSliderEvent(m_tv_volume, config.tv_volume);
@ -1487,6 +1575,22 @@ void GeneralSettings2::ApplyConfig()
else else
m_pad_device->SetSelection(0); m_pad_device->SetSelection(0);
SendSliderEvent(m_input_volume, config.input_volume);
if (!config.input_device.empty() && m_input_device->HasClientObjectData())
{
for (uint32 i = 0; i < m_input_device->GetCount(); ++i)
{
const auto device_description = (wxInputDeviceDescription*)m_input_device->GetClientObject(i);
if (device_description && config.input_device == device_description->GetDescription()->GetIdentifier())
{
m_input_device->SetSelection(i);
break;
}
}
}
else
m_input_device->SetSelection(0);
// account // account
UpdateOnlineAccounts(); UpdateOnlineAccounts();
m_active_account->SetSelection(0); m_active_account->SetSelection(0);
@ -1551,6 +1655,9 @@ void GeneralSettings2::UpdateAudioDevice()
{ {
auto& config = GetConfig(); auto& config = GetConfig();
std::unique_lock lock(g_audioMutex);
std::unique_lock inputLock(g_audioInputMutex);
// tv audio device // tv audio device
{ {
const auto selection = m_tv_device->GetSelection(); const auto selection = m_tv_device->GetSelection();
@ -1560,13 +1667,13 @@ void GeneralSettings2::UpdateAudioDevice()
return; return;
} }
g_tvAudio.reset();
if (m_tv_device->HasClientObjectData()) if (m_tv_device->HasClientObjectData())
{ {
const auto description = (wxDeviceDescription*)m_tv_device->GetClientObject(selection); const auto description = (wxDeviceDescription*)m_tv_device->GetClientObject(selection);
if (description) if (description)
{ {
std::unique_lock lock(g_audioMutex);
sint32 channels; sint32 channels;
if (m_game_launched && g_tvAudio) if (m_game_launched && g_tvAudio)
channels = g_tvAudio->GetChannels(); channels = g_tvAudio->GetChannels();
@ -1588,7 +1695,6 @@ void GeneralSettings2::UpdateAudioDevice()
try try
{ {
g_tvAudio.reset();
g_tvAudio = IAudioAPI::CreateDevice((IAudioAPI::AudioAPI)config.audio_api, description->GetDescription(), 48000, channels, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16); g_tvAudio = IAudioAPI::CreateDevice((IAudioAPI::AudioAPI)config.audio_api, description->GetDescription(), 48000, channels, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16);
g_tvAudio->SetVolume(m_tv_volume->GetValue()); g_tvAudio->SetVolume(m_tv_volume->GetValue());
} }
@ -1609,13 +1715,13 @@ void GeneralSettings2::UpdateAudioDevice()
return; return;
} }
g_padAudio.reset();
if (m_pad_device->HasClientObjectData()) if (m_pad_device->HasClientObjectData())
{ {
const auto description = (wxDeviceDescription*)m_pad_device->GetClientObject(selection); const auto description = (wxDeviceDescription*)m_pad_device->GetClientObject(selection);
if (description) if (description)
{ {
std::unique_lock lock(g_audioMutex);
sint32 channels; sint32 channels;
if (m_game_launched && g_padAudio) if (m_game_launched && g_padAudio)
channels = g_padAudio->GetChannels(); channels = g_padAudio->GetChannels();
@ -1637,7 +1743,6 @@ void GeneralSettings2::UpdateAudioDevice()
try try
{ {
g_padAudio.reset();
g_padAudio = IAudioAPI::CreateDevice((IAudioAPI::AudioAPI)config.audio_api, description->GetDescription(), 48000, channels, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16); g_padAudio = IAudioAPI::CreateDevice((IAudioAPI::AudioAPI)config.audio_api, description->GetDescription(), 48000, channels, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16);
g_padAudio->SetVolume(m_pad_volume->GetValue()); g_padAudio->SetVolume(m_pad_volume->GetValue());
g_padVolume = m_pad_volume->GetValue(); g_padVolume = m_pad_volume->GetValue();
@ -1649,6 +1754,54 @@ void GeneralSettings2::UpdateAudioDevice()
} }
} }
} }
// input audio device
{
const auto selection = m_input_device->GetSelection();
if (selection == wxNOT_FOUND)
{
cemu_assert_debug(false);
return;
}
g_inputAudio.reset();
if (m_input_device->HasClientObjectData())
{
const auto description = (wxInputDeviceDescription*)m_input_device->GetClientObject(selection);
if (description)
{
sint32 channels;
if (m_game_launched && g_inputAudio)
channels = g_inputAudio->GetChannels();
else
{
switch (config.input_channels)
{
case 0:
channels = 1;
break;
case 2:
channels = 6;
break;
default: // stereo
channels = 2;
break;
}
}
try
{
g_inputAudio = IAudioInputAPI::CreateDevice(IAudioInputAPI::AudioInputAPI::Cubeb, description->GetDescription(), 32000, channels, snd_core::AX_SAMPLES_PER_3MS_32KHZ, 16);
g_inputAudio->SetVolume(m_input_volume->GetValue());
}
catch (std::runtime_error& ex)
{
forceLog_printf("can't initialize pad audio: %s", ex.what());
}
}
}
}
} }
void GeneralSettings2::OnAudioDeviceSelected(wxCommandEvent& event) void GeneralSettings2::OnAudioDeviceSelected(wxCommandEvent& event)

View File

@ -58,9 +58,9 @@ private:
// Audio // Audio
wxChoice* m_audio_api; wxChoice* m_audio_api;
wxSlider *m_audio_latency; wxSlider *m_audio_latency;
wxSlider *m_tv_volume, *m_pad_volume; wxSlider *m_tv_volume, *m_pad_volume, *m_input_volume;
wxChoice *m_tv_channels, *m_pad_channels; wxChoice *m_tv_channels, *m_pad_channels, *m_input_channels;
wxChoice *m_tv_device, *m_pad_device; wxChoice *m_tv_device, *m_pad_device, *m_input_device;
// Account // Account
wxButton* m_create_account, * m_delete_account; wxButton* m_create_account, * m_delete_account;

View File

@ -1,4 +1,4 @@
#include "gui/guiWrapper.h" #include "gui/guiWrapper.h"
#include "gui/wxgui.h" #include "gui/wxgui.h"
#include "util/crypto/aes128.h" #include "util/crypto/aes128.h"
#include "gui/MainWindow.h" #include "gui/MainWindow.h"
@ -29,6 +29,7 @@
#include "Cafe/OS/libs/vpad/vpad.h" #include "Cafe/OS/libs/vpad/vpad.h"
#include "audio/IAudioAPI.h" #include "audio/IAudioAPI.h"
#include "audio/IAudioInputAPI.h"
#if BOOST_OS_WINDOWS #if BOOST_OS_WINDOWS
#pragma comment(lib,"Dbghelp.lib") #pragma comment(lib,"Dbghelp.lib")
#endif #endif
@ -223,6 +224,7 @@ void mainEmulatorCommonInit()
rplSymbolStorage_init(); rplSymbolStorage_init();
// static initialization // static initialization
IAudioAPI::InitializeStatic(); IAudioAPI::InitializeStatic();
IAudioInputAPI::InitializeStatic();
// load graphic packs (must happen before config is loaded) // load graphic packs (must happen before config is loaded)
GraphicPack2::LoadAll(); GraphicPack2::LoadAll();
// initialize file system // initialize file system