mirror of
https://github.com/cemu-project/Cemu.git
synced 2024-11-22 17:19:18 +01:00
Implement proper microphone support (#251)
This commit is contained in:
parent
dfa7774c4c
commit
d4e14d2b05
@ -5,6 +5,7 @@
|
||||
#include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h"
|
||||
#include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h"
|
||||
#include "audio/IAudioAPI.h"
|
||||
#include "audio/IAudioInputAPI.h"
|
||||
#include "Cafe/HW/Espresso/Debugger/Debugger.h"
|
||||
|
||||
#include "config/ActiveSettings.h"
|
||||
@ -402,6 +403,7 @@ void cemu_initForGame()
|
||||
GraphicPack2::ActivateForCurrentTitle();
|
||||
// print audio log
|
||||
IAudioAPI::PrintLogging();
|
||||
IAudioInputAPI::PrintLogging();
|
||||
// everything initialized
|
||||
forceLog_printf("------- Run title -------");
|
||||
// wait till GPU thread is initialized
|
||||
|
@ -1,5 +1,7 @@
|
||||
#include "Cafe/OS/common/OSCommon.h"
|
||||
#include "input/InputManager.h"
|
||||
#include "audio/IAudioInputAPI.h"
|
||||
#include "config/CemuConfig.h"
|
||||
|
||||
enum class MIC_RESULT
|
||||
{
|
||||
@ -13,6 +15,7 @@ enum class MIC_RESULT
|
||||
|
||||
#define MIC_SAMPLERATE 32000
|
||||
|
||||
const int MIC_SAMPLES_PER_3MS_32KHZ = (96); // 32000*3/1000
|
||||
|
||||
enum class MIC_STATUS_FLAGS : uint32
|
||||
{
|
||||
@ -22,8 +25,8 @@ enum class MIC_STATUS_FLAGS : uint32
|
||||
};
|
||||
DEFINE_ENUM_FLAG_OPERATORS(MIC_STATUS_FLAGS);
|
||||
|
||||
#define MIC_HANDLE_DRC0 100
|
||||
#define MIC_HANDLE_DRC1 101
|
||||
#define MIC_HANDLE_DRC0 0
|
||||
#define MIC_HANDLE_DRC1 1
|
||||
|
||||
enum class MIC_STATEID
|
||||
{
|
||||
@ -139,6 +142,36 @@ void micExport_MICInit(PPCInterpreter_t* hCPU)
|
||||
// return status
|
||||
memory_writeU32(hCPU->gpr[6], 0); // no error
|
||||
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)
|
||||
@ -172,6 +205,10 @@ void micExport_MICOpen(PPCInterpreter_t* hCPU)
|
||||
// success
|
||||
MICStatus.drc[drcIndex].isOpen = true;
|
||||
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)
|
||||
@ -198,6 +235,10 @@ void micExport_MICClose(PPCInterpreter_t* hCPU)
|
||||
// success
|
||||
MICStatus.drc[drcIndex].isOpen = false;
|
||||
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)
|
||||
@ -360,6 +401,17 @@ void micExport_MICSetDataConsumed(PPCInterpreter_t* hCPU)
|
||||
return;
|
||||
}
|
||||
|
||||
void mic_updateDevicePlayState(bool isPlaying)
|
||||
{
|
||||
if (g_inputAudio)
|
||||
{
|
||||
if (isPlaying)
|
||||
g_inputAudio->Play();
|
||||
else
|
||||
g_inputAudio->Stop();
|
||||
}
|
||||
}
|
||||
|
||||
void mic_updateOnAXFrame()
|
||||
{
|
||||
sint32 drcIndex = 0;
|
||||
@ -368,26 +420,39 @@ void mic_updateOnAXFrame()
|
||||
drcIndex = 1;
|
||||
if (mic_isActive(1) == false)
|
||||
{
|
||||
std::shared_lock lock(g_audioInputMutex);
|
||||
mic_updateDevicePlayState(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const sint32 micSampleCount = 32000/32;
|
||||
sint16 micSampleData[micSampleCount];
|
||||
|
||||
auto controller = InputManager::instance().get_vpad_controller(drcIndex);
|
||||
if( controller && controller->is_mic_active() )
|
||||
std::shared_lock lock(g_audioInputMutex);
|
||||
mic_updateDevicePlayState(true);
|
||||
if (g_inputAudio)
|
||||
{
|
||||
for(sint32 i=0; i<micSampleCount; i++)
|
||||
{
|
||||
micSampleData[i] = (sint16)(sin((float)GetTickCount()*0.1f+sin((float)GetTickCount()*0.0001f)*100.0f)*30000.0f);
|
||||
}
|
||||
sint16 micSampleData[MIC_SAMPLES_PER_3MS_32KHZ];
|
||||
g_inputAudio->ConsumeBlock(micSampleData);
|
||||
mic_feedSamples(0, micSampleData, MIC_SAMPLES_PER_3MS_32KHZ);
|
||||
}
|
||||
else
|
||||
{
|
||||
memset(micSampleData, 0x00, sizeof(micSampleData));
|
||||
const sint32 micSampleCount = 32000 / 32;
|
||||
sint16 micSampleData[micSampleCount];
|
||||
|
||||
auto controller = InputManager::instance().get_vpad_controller(drcIndex);
|
||||
if( controller && controller->is_mic_active() )
|
||||
{
|
||||
for(sint32 i=0; i<micSampleCount; i++)
|
||||
{
|
||||
micSampleData[i] = (sint16)(sin((float)GetTickCount()*0.1f+sin((float)GetTickCount()*0.0001f)*100.0f)*30000.0f);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
memset(micSampleData, 0x00, sizeof(micSampleData));
|
||||
}
|
||||
mic_feedSamples(0, micSampleData, micSampleCount);
|
||||
}
|
||||
mic_feedSamples(0, micSampleData, micSampleCount);
|
||||
}
|
||||
|
||||
namespace mic
|
||||
|
@ -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;
|
@ -295,8 +295,10 @@ void CemuConfig::Load(XMLConfigParser& parser)
|
||||
audio_delay = audio.get("delay", 2);
|
||||
tv_channels = audio.get("TVChannels", kStereo);
|
||||
pad_channels = audio.get("PadChannels", kStereo);
|
||||
input_channels = audio.get("InputChannels", kMono);
|
||||
tv_volume = audio.get("TVVolume", 20);
|
||||
pad_volume = audio.get("PadVolume", 0);
|
||||
input_volume = audio.get("InputVolume", 20);
|
||||
|
||||
const auto tv = audio.get("TVDevice", "");
|
||||
try
|
||||
@ -318,6 +320,16 @@ void CemuConfig::Load(XMLConfigParser& parser)
|
||||
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
|
||||
auto acc = parser.get("Account");
|
||||
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("TVChannels", tv_channels);
|
||||
audio.set("PadChannels", pad_channels);
|
||||
audio.set("InputChannels", input_channels);
|
||||
audio.set("TVVolume", tv_volume);
|
||||
audio.set("PadVolume", pad_volume);
|
||||
audio.set("InputVolume", input_volume);
|
||||
audio.set("TVDevice", boost::nowide::narrow(tv_device).c_str());
|
||||
audio.set("PadDevice", boost::nowide::narrow(pad_device).c_str());
|
||||
audio.set("InputDevice", boost::nowide::narrow(input_device).c_str());
|
||||
|
||||
// account
|
||||
auto acc = config.set("Account");
|
||||
|
@ -462,9 +462,9 @@ struct CemuConfig
|
||||
// audio
|
||||
sint32 audio_api = 0;
|
||||
sint32 audio_delay = 2;
|
||||
AudioChannels tv_channels = kStereo, pad_channels = kStereo;
|
||||
sint32 tv_volume = 50, pad_volume = 0;
|
||||
std::wstring tv_device{ L"default" }, pad_device;
|
||||
AudioChannels tv_channels = kStereo, pad_channels = kStereo, input_channels = kMono;
|
||||
sint32 tv_volume = 50, pad_volume = 0, input_volume = 50;
|
||||
std::wstring tv_device{ L"default" }, pad_device, input_device;
|
||||
|
||||
// account
|
||||
struct
|
||||
|
@ -23,6 +23,8 @@
|
||||
#endif
|
||||
#include "audio/CubebAPI.h"
|
||||
|
||||
#include "audio/IAudioInputAPI.h"
|
||||
|
||||
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h"
|
||||
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h"
|
||||
#include "Cafe/Account/Account.h"
|
||||
@ -70,6 +72,15 @@ private:
|
||||
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
|
||||
{
|
||||
public:
|
||||
@ -424,6 +435,47 @@ wxPanel* GeneralSettings2::AddAudioPage(wxNotebook* notebook)
|
||||
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);
|
||||
return audio_panel;
|
||||
}
|
||||
@ -857,9 +909,12 @@ void GeneralSettings2::StoreConfig()
|
||||
config.tv_channels = (AudioChannels)m_tv_channels->GetSelection();
|
||||
//config.pad_channels = (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.pad_volume = m_pad_volume->GetValue();
|
||||
config.input_volume = m_input_volume->GetValue();
|
||||
|
||||
config.tv_device.clear();
|
||||
const auto tv_device = m_tv_device->GetSelection();
|
||||
@ -879,6 +934,15 @@ void GeneralSettings2::StoreConfig()
|
||||
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
|
||||
config.graphic_api = (GraphicAPI)m_graphic_api->GetSelection();
|
||||
|
||||
@ -977,19 +1041,29 @@ void GeneralSettings2::OnAudioLatencyChanged(wxCommandEvent& event)
|
||||
|
||||
void GeneralSettings2::OnVolumeChanged(wxCommandEvent& event)
|
||||
{
|
||||
std::shared_lock lock(g_audioMutex);
|
||||
if(event.GetEventObject() == m_pad_volume)
|
||||
|
||||
if(event.GetEventObject() == m_input_volume)
|
||||
{
|
||||
if (g_padAudio)
|
||||
{
|
||||
g_padAudio->SetVolume(event.GetInt());
|
||||
g_padVolume = event.GetInt();
|
||||
}
|
||||
std::shared_lock lock(g_audioInputMutex);
|
||||
if (g_inputAudio)
|
||||
g_inputAudio->SetVolume(event.GetInt());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (g_tvAudio)
|
||||
g_tvAudio->SetVolume(event.GetInt());
|
||||
std::shared_lock lock(g_audioMutex);
|
||||
if(event.GetEventObject() == m_pad_volume)
|
||||
{
|
||||
if (g_padAudio)
|
||||
{
|
||||
g_padAudio->SetVolume(event.GetInt());
|
||||
g_padVolume = event.GetInt();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (g_tvAudio)
|
||||
g_tvAudio->SetVolume(event.GetInt());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1048,9 +1122,11 @@ void GeneralSettings2::UpdateAudioDeviceList()
|
||||
{
|
||||
m_tv_device->Clear();
|
||||
m_pad_device->Clear();
|
||||
m_input_device->Clear();
|
||||
|
||||
m_tv_device->Append(_("Disabled"));
|
||||
m_pad_device->Append(_("Disabled"));
|
||||
m_input_device->Append(_("Disabled"));
|
||||
|
||||
const auto audio_api = (IAudioAPI::AudioAPI)GetConfig().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));
|
||||
}
|
||||
|
||||
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)
|
||||
m_tv_device->SetSelection(1);
|
||||
else
|
||||
@ -1067,6 +1151,8 @@ void GeneralSettings2::UpdateAudioDeviceList()
|
||||
|
||||
m_pad_device->SetSelection(0);
|
||||
|
||||
m_input_device->SetSelection(0);
|
||||
|
||||
// todo reset global instance of audio device
|
||||
}
|
||||
|
||||
@ -1453,6 +1539,8 @@ void GeneralSettings2::ApplyConfig()
|
||||
m_tv_channels->SetSelection(config.tv_channels);
|
||||
//m_pad_channels->SetSelection(config.pad_channels);
|
||||
m_pad_channels->SetSelection(0);
|
||||
//m_input_channels->SetSelection(config.pad_channels);
|
||||
m_input_channels->SetSelection(0);
|
||||
|
||||
SendSliderEvent(m_tv_volume, config.tv_volume);
|
||||
|
||||
@ -1487,6 +1575,22 @@ void GeneralSettings2::ApplyConfig()
|
||||
else
|
||||
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
|
||||
UpdateOnlineAccounts();
|
||||
m_active_account->SetSelection(0);
|
||||
@ -1551,6 +1655,9 @@ void GeneralSettings2::UpdateAudioDevice()
|
||||
{
|
||||
auto& config = GetConfig();
|
||||
|
||||
std::unique_lock lock(g_audioMutex);
|
||||
std::unique_lock inputLock(g_audioInputMutex);
|
||||
|
||||
// tv audio device
|
||||
{
|
||||
const auto selection = m_tv_device->GetSelection();
|
||||
@ -1560,13 +1667,13 @@ void GeneralSettings2::UpdateAudioDevice()
|
||||
return;
|
||||
}
|
||||
|
||||
g_tvAudio.reset();
|
||||
|
||||
if (m_tv_device->HasClientObjectData())
|
||||
{
|
||||
const auto description = (wxDeviceDescription*)m_tv_device->GetClientObject(selection);
|
||||
if (description)
|
||||
{
|
||||
std::unique_lock lock(g_audioMutex);
|
||||
|
||||
sint32 channels;
|
||||
if (m_game_launched && g_tvAudio)
|
||||
channels = g_tvAudio->GetChannels();
|
||||
@ -1588,7 +1695,6 @@ void GeneralSettings2::UpdateAudioDevice()
|
||||
|
||||
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->SetVolume(m_tv_volume->GetValue());
|
||||
}
|
||||
@ -1609,13 +1715,13 @@ void GeneralSettings2::UpdateAudioDevice()
|
||||
return;
|
||||
}
|
||||
|
||||
g_padAudio.reset();
|
||||
|
||||
if (m_pad_device->HasClientObjectData())
|
||||
{
|
||||
const auto description = (wxDeviceDescription*)m_pad_device->GetClientObject(selection);
|
||||
if (description)
|
||||
{
|
||||
std::unique_lock lock(g_audioMutex);
|
||||
|
||||
sint32 channels;
|
||||
if (m_game_launched && g_padAudio)
|
||||
channels = g_padAudio->GetChannels();
|
||||
@ -1637,7 +1743,6 @@ void GeneralSettings2::UpdateAudioDevice()
|
||||
|
||||
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->SetVolume(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)
|
||||
|
@ -58,9 +58,9 @@ private:
|
||||
// Audio
|
||||
wxChoice* m_audio_api;
|
||||
wxSlider *m_audio_latency;
|
||||
wxSlider *m_tv_volume, *m_pad_volume;
|
||||
wxChoice *m_tv_channels, *m_pad_channels;
|
||||
wxChoice *m_tv_device, *m_pad_device;
|
||||
wxSlider *m_tv_volume, *m_pad_volume, *m_input_volume;
|
||||
wxChoice *m_tv_channels, *m_pad_channels, *m_input_channels;
|
||||
wxChoice *m_tv_device, *m_pad_device, *m_input_device;
|
||||
|
||||
// Account
|
||||
wxButton* m_create_account, * m_delete_account;
|
||||
|
@ -1,4 +1,4 @@
|
||||
#include "gui/guiWrapper.h"
|
||||
#include "gui/guiWrapper.h"
|
||||
#include "gui/wxgui.h"
|
||||
#include "util/crypto/aes128.h"
|
||||
#include "gui/MainWindow.h"
|
||||
@ -29,6 +29,7 @@
|
||||
#include "Cafe/OS/libs/vpad/vpad.h"
|
||||
|
||||
#include "audio/IAudioAPI.h"
|
||||
#include "audio/IAudioInputAPI.h"
|
||||
#if BOOST_OS_WINDOWS
|
||||
#pragma comment(lib,"Dbghelp.lib")
|
||||
#endif
|
||||
@ -223,6 +224,7 @@ void mainEmulatorCommonInit()
|
||||
rplSymbolStorage_init();
|
||||
// static initialization
|
||||
IAudioAPI::InitializeStatic();
|
||||
IAudioInputAPI::InitializeStatic();
|
||||
// load graphic packs (must happen before config is loaded)
|
||||
GraphicPack2::LoadAll();
|
||||
// initialize file system
|
||||
|
Loading…
Reference in New Issue
Block a user