Address review comments
This commit is contained in:
parent
c669aa8d55
commit
5f532c2560
|
@ -9,11 +9,13 @@
|
||||||
|
|
||||||
namespace AudioCore {
|
namespace AudioCore {
|
||||||
|
|
||||||
|
using SampleQueue = Common::SPSCQueue<Frontend::Mic::Samples>;
|
||||||
|
|
||||||
struct CubebInput::Impl {
|
struct CubebInput::Impl {
|
||||||
cubeb* ctx = nullptr;
|
cubeb* ctx = nullptr;
|
||||||
cubeb_stream* stream = nullptr;
|
cubeb_stream* stream = nullptr;
|
||||||
|
|
||||||
std::unique_ptr<Frontend::Mic::SampleQueue> sample_queue{};
|
std::unique_ptr<SampleQueue> sample_queue{};
|
||||||
u8 sample_size_in_bytes = 0;
|
u8 sample_size_in_bytes = 0;
|
||||||
|
|
||||||
static long DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer,
|
static long DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer,
|
||||||
|
@ -26,7 +28,7 @@ CubebInput::CubebInput() : impl(std::make_unique<Impl>()) {
|
||||||
LOG_ERROR(Audio, "cubeb_init failed! Mic will not work properly");
|
LOG_ERROR(Audio, "cubeb_init failed! Mic will not work properly");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
impl->sample_queue = std::make_unique<Frontend::Mic::SampleQueue>();
|
impl->sample_queue = std::make_unique<SampleQueue>();
|
||||||
}
|
}
|
||||||
|
|
||||||
CubebInput::~CubebInput() {
|
CubebInput::~CubebInput() {
|
||||||
|
@ -40,7 +42,7 @@ CubebInput::~CubebInput() {
|
||||||
cubeb_destroy(impl->ctx);
|
cubeb_destroy(impl->ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CubebInput::StartSampling(Frontend::Mic::Parameters params) {
|
void CubebInput::StartSampling(const Frontend::Mic::Parameters& params) {
|
||||||
// Cubeb apparently only supports signed 16 bit PCM (and float32 which the 3ds doesn't support)
|
// Cubeb apparently only supports signed 16 bit PCM (and float32 which the 3ds doesn't support)
|
||||||
// TODO resample the input stream
|
// TODO resample the input stream
|
||||||
if (params.sign == Frontend::Mic::Signedness::Unsigned) {
|
if (params.sign == Frontend::Mic::Signedness::Unsigned) {
|
||||||
|
@ -103,12 +105,11 @@ Frontend::Mic::Samples CubebInput::Read() {
|
||||||
long CubebInput::Impl::DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer,
|
long CubebInput::Impl::DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer,
|
||||||
void* output_buffer, long num_frames) {
|
void* output_buffer, long num_frames) {
|
||||||
Impl* impl = static_cast<Impl*>(user_data);
|
Impl* impl = static_cast<Impl*>(user_data);
|
||||||
u8 const* data = reinterpret_cast<u8 const*>(input_buffer);
|
|
||||||
|
|
||||||
if (!impl) {
|
if (!impl) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u8 const* data = reinterpret_cast<u8 const*>(input_buffer);
|
||||||
impl->sample_queue->Push(std::vector(data, data + num_frames * impl->sample_size_in_bytes));
|
impl->sample_queue->Push(std::vector(data, data + num_frames * impl->sample_size_in_bytes));
|
||||||
|
|
||||||
// returning less than num_frames here signals cubeb to stop sampling
|
// returning less than num_frames here signals cubeb to stop sampling
|
||||||
|
@ -130,7 +131,7 @@ std::vector<std::string> ListCubebInputDevices() {
|
||||||
if (cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_INPUT, &collection) != CUBEB_OK) {
|
if (cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_INPUT, &collection) != CUBEB_OK) {
|
||||||
LOG_WARNING(Audio_Sink, "Audio input device enumeration not supported");
|
LOG_WARNING(Audio_Sink, "Audio input device enumeration not supported");
|
||||||
} else {
|
} else {
|
||||||
for (size_t i = 0; i < collection.count; i++) {
|
for (std::size_t i = 0; i < collection.count; i++) {
|
||||||
const cubeb_device_info& device = collection.device[i];
|
const cubeb_device_info& device = collection.device[i];
|
||||||
if (device.state == CUBEB_DEVICE_STATE_ENABLED && device.friendly_name) {
|
if (device.state == CUBEB_DEVICE_STATE_ENABLED && device.friendly_name) {
|
||||||
device_list.emplace_back(device.friendly_name);
|
device_list.emplace_back(device.friendly_name);
|
||||||
|
|
|
@ -15,7 +15,7 @@ public:
|
||||||
CubebInput();
|
CubebInput();
|
||||||
~CubebInput() override;
|
~CubebInput() override;
|
||||||
|
|
||||||
void StartSampling(Frontend::Mic::Parameters params) override;
|
void StartSampling(const Frontend::Mic::Parameters& params) override;
|
||||||
|
|
||||||
void StopSampling() override;
|
void StopSampling() override;
|
||||||
|
|
||||||
|
|
|
@ -166,6 +166,10 @@ void Config::ReadValues() {
|
||||||
sdl2_config->GetBoolean("Audio", "enable_audio_stretching", true);
|
sdl2_config->GetBoolean("Audio", "enable_audio_stretching", true);
|
||||||
Settings::values.audio_device_id = sdl2_config->GetString("Audio", "output_device", "auto");
|
Settings::values.audio_device_id = sdl2_config->GetString("Audio", "output_device", "auto");
|
||||||
Settings::values.volume = sdl2_config->GetReal("Audio", "volume", 1);
|
Settings::values.volume = sdl2_config->GetReal("Audio", "volume", 1);
|
||||||
|
Settings::values.mic_input_device =
|
||||||
|
sdl2_config->GetString("Audio", "mic_input_device", "Default");
|
||||||
|
Settings::values.mic_input_type =
|
||||||
|
static_cast<Settings::MicInputType>(sdl2_config->GetInteger("Audio", "mic_input_type", 0));
|
||||||
|
|
||||||
// Data Storage
|
// Data Storage
|
||||||
Settings::values.use_virtual_sd =
|
Settings::values.use_virtual_sd =
|
||||||
|
|
|
@ -198,7 +198,8 @@ void Config::ReadValues() {
|
||||||
Settings::values.audio_device_id =
|
Settings::values.audio_device_id =
|
||||||
ReadSetting("output_device", "auto").toString().toStdString();
|
ReadSetting("output_device", "auto").toString().toStdString();
|
||||||
Settings::values.volume = ReadSetting("volume", 1).toFloat();
|
Settings::values.volume = ReadSetting("volume", 1).toFloat();
|
||||||
Settings::values.mic_input_type = ReadSetting("mic_input_type", 0).toInt();
|
Settings::values.mic_input_type =
|
||||||
|
static_cast<Settings::MicInputType>(ReadSetting("mic_input_type", 0).toInt());
|
||||||
Settings::values.mic_input_device =
|
Settings::values.mic_input_device =
|
||||||
ReadSetting("mic_input_device", "Default").toString().toStdString();
|
ReadSetting("mic_input_device", "Default").toString().toStdString();
|
||||||
qt_config->endGroup();
|
qt_config->endGroup();
|
||||||
|
@ -483,8 +484,8 @@ void Config::SaveValues() {
|
||||||
WriteSetting("enable_audio_stretching", Settings::values.enable_audio_stretching, true);
|
WriteSetting("enable_audio_stretching", Settings::values.enable_audio_stretching, true);
|
||||||
WriteSetting("output_device", QString::fromStdString(Settings::values.audio_device_id), "auto");
|
WriteSetting("output_device", QString::fromStdString(Settings::values.audio_device_id), "auto");
|
||||||
WriteSetting("volume", Settings::values.volume, 1.0f);
|
WriteSetting("volume", Settings::values.volume, 1.0f);
|
||||||
WriteSetting("mic_input_device", QString::fromStdString(Settings::values.mic_input_device), 0);
|
WriteSetting("mic_input_device", QString::fromStdString(Settings::values.mic_input_device), "Default");
|
||||||
WriteSetting("mic_input_type", Settings::values.mic_input_type, "Default");
|
WriteSetting("mic_input_type", static_cast<int>(Settings::values.mic_input_type), 0);
|
||||||
qt_config->endGroup();
|
qt_config->endGroup();
|
||||||
|
|
||||||
using namespace Service::CAM;
|
using namespace Service::CAM;
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <QAudioDeviceInfo>
|
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
#include "audio_core/cubeb_input.h"
|
#include "audio_core/cubeb_input.h"
|
||||||
#include "audio_core/sink.h"
|
#include "audio_core/sink.h"
|
||||||
|
@ -37,14 +36,14 @@ ConfigureAudio::ConfigureAudio(QWidget* parent)
|
||||||
ui->input_device_combo_box->addItem(QString::fromStdString(device));
|
ui->input_device_combo_box->addItem(QString::fromStdString(device));
|
||||||
}
|
}
|
||||||
|
|
||||||
connect(ui->input_type_combo_box, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
connect(ui->input_type_combo_box, qOverload<int>(&QComboBox::currentIndexChanged), this,
|
||||||
&ConfigureAudio::updateAudioInputDevices);
|
&ConfigureAudio::updateAudioInputDevices);
|
||||||
|
|
||||||
ui->input_type_combo_box->setEnabled(!Core::System::GetInstance().IsPoweredOn());
|
ui->input_type_combo_box->setEnabled(!Core::System::GetInstance().IsPoweredOn());
|
||||||
ui->input_device_combo_box->setEnabled(!Core::System::GetInstance().IsPoweredOn());
|
ui->input_device_combo_box->setEnabled(!Core::System::GetInstance().IsPoweredOn());
|
||||||
|
|
||||||
this->setConfiguration();
|
this->setConfiguration();
|
||||||
connect(ui->output_sink_combo_box, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
connect(ui->output_sink_combo_box, qOverload<int>(&QComboBox::currentIndexChanged), this,
|
||||||
&ConfigureAudio::updateAudioOutputDevices);
|
&ConfigureAudio::updateAudioOutputDevices);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,10 +73,11 @@ void ConfigureAudio::setConfiguration() {
|
||||||
}
|
}
|
||||||
ui->emulation_combo_box->setCurrentIndex(selection);
|
ui->emulation_combo_box->setCurrentIndex(selection);
|
||||||
|
|
||||||
ui->input_type_combo_box->setCurrentIndex(Settings::values.mic_input_type);
|
int index = static_cast<int>(Settings::values.mic_input_type);
|
||||||
|
ui->input_type_combo_box->setCurrentIndex(index);
|
||||||
ui->input_device_combo_box->setCurrentText(
|
ui->input_device_combo_box->setCurrentText(
|
||||||
QString::fromStdString(Settings::values.mic_input_device));
|
QString::fromStdString(Settings::values.mic_input_device));
|
||||||
updateAudioInputDevices(Settings::values.mic_input_type);
|
updateAudioInputDevices(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureAudio::setOutputSinkFromSinkID() {
|
void ConfigureAudio::setOutputSinkFromSinkID() {
|
||||||
|
@ -124,7 +124,8 @@ void ConfigureAudio::applyConfiguration() {
|
||||||
static_cast<float>(ui->volume_slider->sliderPosition()) / ui->volume_slider->maximum();
|
static_cast<float>(ui->volume_slider->sliderPosition()) / ui->volume_slider->maximum();
|
||||||
Settings::values.enable_dsp_lle = ui->emulation_combo_box->currentIndex() != 0;
|
Settings::values.enable_dsp_lle = ui->emulation_combo_box->currentIndex() != 0;
|
||||||
Settings::values.enable_dsp_lle_multithread = ui->emulation_combo_box->currentIndex() == 2;
|
Settings::values.enable_dsp_lle_multithread = ui->emulation_combo_box->currentIndex() == 2;
|
||||||
Settings::values.mic_input_type = ui->input_type_combo_box->currentIndex();
|
Settings::values.mic_input_type =
|
||||||
|
static_cast<Settings::MicInputType>(ui->input_type_combo_box->currentIndex());
|
||||||
Settings::values.mic_input_device = ui->input_device_combo_box->currentText().toStdString();
|
Settings::values.mic_input_device = ui->input_device_combo_box->currentText().toStdString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2018 Citra Emulator Project
|
// Copyright 2019 Citra Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
@ -12,39 +12,28 @@ constexpr std::array<u8, 32> NOISE_SAMPLE_8_BIT = {
|
||||||
0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF5, 0xFF, 0xFF, 0xFF, 0xFF, 0x8E, 0xFF,
|
0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF5, 0xFF, 0xFF, 0xFF, 0xFF, 0x8E, 0xFF,
|
||||||
0xF4, 0xE1, 0xBF, 0x9A, 0x71, 0x58, 0x5B, 0x5F, 0x62, 0xC2, 0x25, 0x05, 0x01, 0x01, 0x01, 0x01};
|
0xF4, 0xE1, 0xBF, 0x9A, 0x71, 0x58, 0x5B, 0x5F, 0x62, 0xC2, 0x25, 0x05, 0x01, 0x01, 0x01, 0x01};
|
||||||
|
|
||||||
constexpr std::array<u8, 512> NOISE_SAMPLE_16_BIT = {
|
constexpr std::array<u8, 32> NOISE_SAMPLE_16_BIT = {
|
||||||
0x64, 0x61, 0x74, 0x61, 0x56, 0xD7, 0x00, 0x00, 0x48, 0xF7, 0x86, 0x05, 0x77, 0x1A, 0xF4, 0x1F,
|
0x64, 0x61, 0x74, 0x61, 0x56, 0xD7, 0x00, 0x00, 0x48, 0xF7, 0x86, 0x05, 0x77, 0x1A, 0xF4, 0x1F,
|
||||||
0x28, 0x0F, 0x6B, 0xEB, 0x1C, 0xC0, 0xCB, 0x9D, 0x46, 0x90, 0xDF, 0x98, 0xEA, 0xAE, 0xB5, 0xC4,
|
0x28, 0x0F, 0x6B, 0xEB, 0x1C, 0xC0, 0xCB, 0x9D, 0x46, 0x90, 0xDF, 0x98, 0xEA, 0xAE, 0xB5, 0xC4};
|
||||||
0x9D, 0xCE, 0xB6, 0xC9, 0xDF, 0xBD, 0x82, 0xBA, 0x83, 0xCD, 0x57, 0xF9, 0x96, 0x30, 0x2C, 0x5B,
|
|
||||||
0x29, 0x64, 0xD3, 0x46, 0x0D, 0x12, 0x3E, 0xDE, 0x00, 0xBD, 0x74, 0xAE, 0xF0, 0xA4, 0x91, 0x93,
|
Interface::~Interface() = default;
|
||||||
0x02, 0x80, 0x00, 0x80, 0x03, 0x80, 0x6F, 0xAA, 0x8E, 0xE8, 0xEE, 0x1A, 0x2B, 0x2C, 0xC6, 0x18,
|
|
||||||
0xF8, 0xED, 0xC2, 0xBE, 0x00, 0x99, 0xC1, 0x82, 0x00, 0x80, 0xA6, 0x8A, 0x37, 0xA8, 0x6B, 0xCE,
|
void NullMic::StartSampling(const Parameters& params) {
|
||||||
0x20, 0xEF, 0xD0, 0xFD, 0x68, 0xF9, 0x50, 0xEF, 0x89, 0xF2, 0xDE, 0x0C, 0x24, 0x36, 0xDB, 0x58,
|
parameters = params;
|
||||||
0xB2, 0x61, 0xD5, 0x4C, 0x0B, 0x27, 0x6D, 0x02, 0xDC, 0xE8, 0x13, 0xD8, 0x2F, 0xC9, 0x07, 0xBC,
|
is_sampling = true;
|
||||||
0xAB, 0xBA, 0x70, 0xD1, 0xDE, 0x01, 0xF2, 0x3C, 0x64, 0x6A, 0xFF, 0x78, 0x47, 0x6B, 0x26, 0x56,
|
}
|
||||||
0x9B, 0x51, 0xF3, 0x65, 0xFF, 0x7F, 0xFF, 0x7F, 0x88, 0x6B, 0x46, 0x24, 0xBF, 0xD8, 0x8C, 0xB4,
|
|
||||||
0xD9, 0xCF, 0x77, 0x1C, 0x43, 0x6B, 0xFE, 0x7F, 0x15, 0x64, 0xA6, 0x13, 0x03, 0xCE, 0x51, 0xBF,
|
void NullMic::StopSampling() {
|
||||||
0xC7, 0xEB, 0xCB, 0x2E, 0x6C, 0x58, 0xA7, 0x51, 0x40, 0x2A, 0x24, 0x06, 0x45, 0xFB, 0xD3, 0xFE,
|
is_sampling = false;
|
||||||
0x51, 0xF2, 0x1E, 0xC5, 0x79, 0x8A, 0x00, 0x80, 0x94, 0x8E, 0x7A, 0xDE, 0x83, 0x29, 0x8E, 0x3C,
|
}
|
||||||
0x4B, 0x0F, 0xD8, 0xCB, 0x41, 0xAB, 0xC1, 0xC5, 0xC6, 0xFE, 0x0F, 0x1F, 0x92, 0x05, 0x4B, 0xC4,
|
|
||||||
0xDC, 0x8F, 0xE1, 0x90, 0xE3, 0xC0, 0xDB, 0xF1, 0xCC, 0xF8, 0xC8, 0xD4, 0x2A, 0xAF, 0xB7, 0xB5,
|
void NullMic::AdjustSampleRate(u32 sample_rate) {
|
||||||
0x7F, 0xF0, 0xB2, 0x39, 0xD4, 0x5C, 0xD6, 0x41, 0x69, 0xFD, 0xEA, 0xBB, 0x57, 0x9B, 0x59, 0x98,
|
parameters.sample_rate = sample_rate;
|
||||||
0x8E, 0x9B, 0x8A, 0x97, 0x54, 0x98, 0xE6, 0xB5, 0x25, 0xF8, 0x77, 0x48, 0xFF, 0x7F, 0xFF, 0x7F,
|
}
|
||||||
0x49, 0x6B, 0xB4, 0x4B, 0xE9, 0x45, 0xE3, 0x57, 0x60, 0x64, 0x8C, 0x4D, 0x69, 0x10, 0x10, 0xCA,
|
|
||||||
0x13, 0xA4, 0xC5, 0xB7, 0xE4, 0xFD, 0x7C, 0x54, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xE0, 0x52,
|
Samples NullMic::Read() {
|
||||||
0x5E, 0x22, 0x31, 0x0B, 0xB5, 0x12, 0xB1, 0x2E, 0xA7, 0x4D, 0x17, 0x61, 0x46, 0x65, 0xA5, 0x5F,
|
return {};
|
||||||
0x5C, 0x57, 0xB6, 0x4C, 0x3B, 0x38, 0xFD, 0x11, 0x70, 0xDC, 0x2A, 0xA8, 0xB9, 0x8C, 0xEA, 0x98,
|
}
|
||||||
0x07, 0xC6, 0xDF, 0xF7, 0x2D, 0x0C, 0xEC, 0xF0, 0xA8, 0xB2, 0x02, 0x80, 0x00, 0x80, 0xBA, 0x8A,
|
|
||||||
0x0E, 0xCB, 0x54, 0xF7, 0x2B, 0xEE, 0x4C, 0xB9, 0x48, 0x89, 0x8F, 0x90, 0x0E, 0xD8, 0x83, 0x32,
|
|
||||||
0xC5, 0x5C, 0x54, 0x34, 0x86, 0xD6, 0x78, 0x8C, 0x88, 0x90, 0x87, 0xDE, 0x75, 0x37, 0x55, 0x56,
|
|
||||||
0xF1, 0x28, 0xF0, 0xDA, 0xE2, 0xAB, 0xE6, 0xB6, 0xD6, 0xDF, 0x4D, 0xF4, 0xCF, 0xDE, 0x3E, 0xBA,
|
|
||||||
0xC6, 0xB3, 0x81, 0xDA, 0xFE, 0x0D, 0xE9, 0x1D, 0xCE, 0xFB, 0x59, 0xCD, 0x57, 0xCA, 0x77, 0x06,
|
|
||||||
0x63, 0x5A, 0xFE, 0x7F, 0x49, 0x63, 0x7F, 0x14, 0x0E, 0xDB, 0x2A, 0xE5, 0x3B, 0x27, 0xFF, 0x69,
|
|
||||||
0x0A, 0x7C, 0xC5, 0x56, 0x65, 0x19, 0xEC, 0xE5, 0x0E, 0xC6, 0xA6, 0xB0, 0x09, 0xA2, 0x06, 0xA8,
|
|
||||||
0xC7, 0xD1, 0xE9, 0x15, 0xD5, 0x4E, 0xBB, 0x56, 0x85, 0x2A, 0x25, 0xF0, 0x78, 0xD6, 0x70, 0xEB,
|
|
||||||
0xF2, 0x0F, 0xE1, 0x15, 0x66, 0xEC, 0xC7, 0xB0, 0xE7, 0x93, 0x9D, 0xAD, 0xD1, 0xE6, 0xD3, 0x0D,
|
|
||||||
0xAC, 0x00, 0xB2, 0xC7, 0x5D, 0x8B, 0x00, 0x80, 0xA1, 0x88, 0x14, 0xBE, 0xDF, 0xFB, 0xCF, 0x32,
|
|
||||||
0xD8, 0x5B, 0x0F, 0x6F, 0x6C, 0x62, 0xD9, 0x33, 0x76, 0xF3, 0xD6, 0xBF, 0x41, 0xB3, 0x5C, 0xD1};
|
|
||||||
|
|
||||||
StaticMic::StaticMic()
|
StaticMic::StaticMic()
|
||||||
: CACHE_8_BIT{NOISE_SAMPLE_8_BIT.begin(), NOISE_SAMPLE_8_BIT.end()},
|
: CACHE_8_BIT{NOISE_SAMPLE_8_BIT.begin(), NOISE_SAMPLE_8_BIT.end()},
|
||||||
|
@ -52,7 +41,7 @@ StaticMic::StaticMic()
|
||||||
|
|
||||||
StaticMic::~StaticMic() = default;
|
StaticMic::~StaticMic() = default;
|
||||||
|
|
||||||
void StaticMic::StartSampling(Parameters params) {
|
void StaticMic::StartSampling(const Parameters& params) {
|
||||||
sample_rate = params.sample_rate;
|
sample_rate = params.sample_rate;
|
||||||
sample_size = params.sample_size;
|
sample_size = params.sample_size;
|
||||||
|
|
||||||
|
@ -70,17 +59,4 @@ Samples StaticMic::Read() {
|
||||||
return (sample_size == 8) ? CACHE_8_BIT : CACHE_16_BIT;
|
return (sample_size == 8) ? CACHE_8_BIT : CACHE_16_BIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::shared_ptr<Mic::Interface> current_mic;
|
|
||||||
|
|
||||||
void RegisterMic(std::shared_ptr<Mic::Interface> mic) {
|
|
||||||
current_mic = mic;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<Mic::Interface> GetCurrentMic() {
|
|
||||||
if (!current_mic) {
|
|
||||||
current_mic = std::make_shared<Mic::NullMic>();
|
|
||||||
}
|
|
||||||
return current_mic;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Frontend::Mic
|
} // namespace Frontend::Mic
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2018 Citra Emulator Project
|
// Copyright 2019 Citra Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
@ -17,7 +17,6 @@ enum class Signedness : u8 {
|
||||||
};
|
};
|
||||||
|
|
||||||
using Samples = std::vector<u8>;
|
using Samples = std::vector<u8>;
|
||||||
using SampleQueue = Common::SPSCQueue<Samples>;
|
|
||||||
|
|
||||||
struct Parameters {
|
struct Parameters {
|
||||||
Signedness sign;
|
Signedness sign;
|
||||||
|
@ -32,17 +31,19 @@ class Interface {
|
||||||
public:
|
public:
|
||||||
Interface() = default;
|
Interface() = default;
|
||||||
|
|
||||||
virtual ~Interface() = default;
|
virtual ~Interface();
|
||||||
|
|
||||||
/// Starts the microphone. Called by Core
|
/// Starts the microphone. Called by Core
|
||||||
virtual void StartSampling(Parameters params) = 0;
|
virtual void StartSampling(const Parameters& params) = 0;
|
||||||
|
|
||||||
/// Stops the microphone. Called by Core
|
/// Stops the microphone. Called by Core
|
||||||
virtual void StopSampling() = 0;
|
virtual void StopSampling() = 0;
|
||||||
|
|
||||||
/// Called from the actual event timing read back. The frontend impl is responsible for wrapping
|
/**
|
||||||
/// up any data and returning them to the core so the core can write them to the sharedmem. If
|
* Called from the actual event timing at a constant period under a given sample rate.
|
||||||
/// theres nothing to return just return an empty vector
|
* When sampling is enabled this function is expected to return a buffer of 16 samples in ideal
|
||||||
|
* conditions, but can be lax if the data is coming in from another source like a real mic.
|
||||||
|
*/
|
||||||
virtual Samples Read() = 0;
|
virtual Samples Read() = 0;
|
||||||
|
|
||||||
/// Adjusts the Parameters. Implementations should update the parameters field in addition to
|
/// Adjusts the Parameters. Implementations should update the parameters field in addition to
|
||||||
|
@ -70,7 +71,7 @@ public:
|
||||||
return is_sampling;
|
return is_sampling;
|
||||||
}
|
}
|
||||||
|
|
||||||
Parameters GetParameters() const {
|
const Parameters& GetParameters() const {
|
||||||
return parameters;
|
return parameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,22 +84,13 @@ protected:
|
||||||
|
|
||||||
class NullMic final : public Interface {
|
class NullMic final : public Interface {
|
||||||
public:
|
public:
|
||||||
void StartSampling(Parameters params) override {
|
void StartSampling(const Parameters& params) override;
|
||||||
parameters = params;
|
|
||||||
is_sampling = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void StopSampling() override {
|
void StopSampling() override;
|
||||||
is_sampling = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AdjustSampleRate(u32 sample_rate) override {
|
void AdjustSampleRate(u32 sample_rate) override;
|
||||||
parameters.sample_rate = sample_rate;
|
|
||||||
}
|
|
||||||
|
|
||||||
Samples Read() override {
|
Samples Read() override;
|
||||||
return {};
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class StaticMic final : public Interface {
|
class StaticMic final : public Interface {
|
||||||
|
@ -106,7 +98,7 @@ public:
|
||||||
StaticMic();
|
StaticMic();
|
||||||
~StaticMic() override;
|
~StaticMic() override;
|
||||||
|
|
||||||
void StartSampling(Parameters params) override;
|
void StartSampling(const Parameters& params) override;
|
||||||
void StopSampling() override;
|
void StopSampling() override;
|
||||||
void AdjustSampleRate(u32 sample_rate) override;
|
void AdjustSampleRate(u32 sample_rate) override;
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,8 @@ enum class Encoding : u8 {
|
||||||
PCM16Signed = 3, ///< Signed 16-bit PCM.
|
PCM16Signed = 3, ///< Signed 16-bit PCM.
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Microphone audio sampling rates.
|
/// Microphone audio sampling rates. The actual accurate sampling rate can be calculated using
|
||||||
|
/// (16756991 / 512) / (SampleRate + 1) where SampleRate is one of the above values.
|
||||||
enum class SampleRate : u8 {
|
enum class SampleRate : u8 {
|
||||||
Rate32730 = 0, ///< 32728.498 Hz
|
Rate32730 = 0, ///< 32728.498 Hz
|
||||||
Rate16360 = 1, ///< 16364.479 Hz
|
Rate16360 = 1, ///< 16364.479 Hz
|
||||||
|
@ -34,13 +35,13 @@ enum class SampleRate : u8 {
|
||||||
constexpr u32 GetSampleRateInHz(SampleRate sample_rate) {
|
constexpr u32 GetSampleRateInHz(SampleRate sample_rate) {
|
||||||
switch (sample_rate) {
|
switch (sample_rate) {
|
||||||
case SampleRate::Rate8180:
|
case SampleRate::Rate8180:
|
||||||
return 8180;
|
return 8182;
|
||||||
case SampleRate::Rate10910:
|
case SampleRate::Rate10910:
|
||||||
return 10910;
|
return 10909;
|
||||||
case SampleRate::Rate16360:
|
case SampleRate::Rate16360:
|
||||||
return 16360;
|
return 16364;
|
||||||
case SampleRate::Rate32730:
|
case SampleRate::Rate32730:
|
||||||
return 32730;
|
return 32728;
|
||||||
default:
|
default:
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
}
|
}
|
||||||
|
@ -54,45 +55,28 @@ constexpr u64 BufferUpdateRate16360 = BASE_CLOCK_RATE_ARM11 / 1022;
|
||||||
constexpr u64 BufferUpdateRate32730 = BASE_CLOCK_RATE_ARM11 / 2045;
|
constexpr u64 BufferUpdateRate32730 = BASE_CLOCK_RATE_ARM11 / 2045;
|
||||||
|
|
||||||
constexpr u64 GetBufferUpdateRate(SampleRate sample_rate) {
|
constexpr u64 GetBufferUpdateRate(SampleRate sample_rate) {
|
||||||
switch (sample_rate) {
|
return GetSampleRateInHz(sample_rate) / 16;
|
||||||
case SampleRate::Rate8180:
|
|
||||||
return BufferUpdateRate8180;
|
|
||||||
case SampleRate::Rate10910:
|
|
||||||
return BufferUpdateRate10910;
|
|
||||||
case SampleRate::Rate16360:
|
|
||||||
return BufferUpdateRate16360;
|
|
||||||
case SampleRate::Rate32730:
|
|
||||||
return BufferUpdateRate32730;
|
|
||||||
default:
|
|
||||||
UNREACHABLE();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Variables holding the current mic buffer writing state
|
// Variables holding the current mic buffer writing state
|
||||||
struct State {
|
struct State {
|
||||||
u8* sharedmem_buffer = nullptr;
|
u8* sharedmem_buffer = nullptr;
|
||||||
u32 sharedmem_size = 0;
|
u32 sharedmem_size = 0;
|
||||||
size_t size = 0;
|
std::size_t size = 0;
|
||||||
u32 offset = 0;
|
u32 offset = 0;
|
||||||
u32 initial_offset = 0;
|
u32 initial_offset = 0;
|
||||||
bool looped_buffer = false;
|
bool looped_buffer = false;
|
||||||
u8 sample_size = 0;
|
u8 sample_size = 0;
|
||||||
SampleRate sample_rate = SampleRate::Rate16360;
|
SampleRate sample_rate = SampleRate::Rate16360;
|
||||||
|
|
||||||
void UpdateOffset() {
|
|
||||||
// The last 4 bytes of the shared memory contains the latest offset
|
|
||||||
// so update that as well https://www.3dbrew.org/wiki/MIC_Shared_Memory
|
|
||||||
std::memcpy(sharedmem_buffer + (sharedmem_size - sizeof(u32)),
|
|
||||||
reinterpret_cast<u8*>(&offset), sizeof(u32));
|
|
||||||
}
|
|
||||||
|
|
||||||
void WriteSamples(const std::vector<u8>& samples) {
|
void WriteSamples(const std::vector<u8>& samples) {
|
||||||
u32 bytes_total_written = 0;
|
u32 bytes_total_written = 0;
|
||||||
const size_t remaining_space = size - offset;
|
const std::size_t remaining_space = size - offset;
|
||||||
size_t bytes_to_write = std::min(samples.size(), remaining_space);
|
std::size_t bytes_to_write = std::min(samples.size(), remaining_space);
|
||||||
|
|
||||||
// Write as many samples as we can to the buffer.
|
// Write as many samples as we can to the buffer.
|
||||||
// TODO if the sample size is 16bit, this could theoretically cut a sample
|
// TODO if the sample size is 16bit, this could theoretically cut a sample in the case where
|
||||||
|
// the application configures an odd size
|
||||||
std::memcpy(sharedmem_buffer + offset, samples.data(), bytes_to_write);
|
std::memcpy(sharedmem_buffer + offset, samples.data(), bytes_to_write);
|
||||||
offset += static_cast<u32>(bytes_to_write);
|
offset += static_cast<u32>(bytes_to_write);
|
||||||
bytes_total_written += static_cast<u32>(bytes_to_write);
|
bytes_total_written += static_cast<u32>(bytes_to_write);
|
||||||
|
@ -105,6 +89,12 @@ struct State {
|
||||||
bytes_to_write);
|
bytes_to_write);
|
||||||
offset += static_cast<u32>(bytes_to_write);
|
offset += static_cast<u32>(bytes_to_write);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The last 4 bytes of the shared memory contains the latest offset
|
||||||
|
// so update that as well https://www.3dbrew.org/wiki/MIC_Shared_Memory
|
||||||
|
u32_le off = offset;
|
||||||
|
std::memcpy(sharedmem_buffer + (sharedmem_size - sizeof(u32)), reinterpret_cast<u8*>(&off),
|
||||||
|
sizeof(u32));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -112,9 +102,10 @@ struct MIC_U::Impl {
|
||||||
explicit Impl(Core::System& system) : timing(system.CoreTiming()) {
|
explicit Impl(Core::System& system) : timing(system.CoreTiming()) {
|
||||||
buffer_full_event =
|
buffer_full_event =
|
||||||
system.Kernel().CreateEvent(Kernel::ResetType::OneShot, "MIC_U::buffer_full_event");
|
system.Kernel().CreateEvent(Kernel::ResetType::OneShot, "MIC_U::buffer_full_event");
|
||||||
buffer_write_event = timing.RegisterEvent(
|
buffer_write_event =
|
||||||
"MIC_U::UpdateBuffer", std::bind(&Impl::UpdateSharedMemBuffer, this,
|
timing.RegisterEvent("MIC_U::UpdateBuffer", [this](u64 userdata, s64 cycles_late) {
|
||||||
std::placeholders::_1, std::placeholders::_2));
|
UpdateSharedMemBuffer(userdata, cycles_late);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void MapSharedMem(Kernel::HLERequestContext& ctx) {
|
void MapSharedMem(Kernel::HLERequestContext& ctx) {
|
||||||
|
@ -131,7 +122,7 @@ struct MIC_U::Impl {
|
||||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||||
rb.Push(RESULT_SUCCESS);
|
rb.Push(RESULT_SUCCESS);
|
||||||
|
|
||||||
LOG_TRACE(Service_MIC, "MIC:U MapSharedMem called, size=0x{:X}", size);
|
LOG_TRACE(Service_MIC, "MapSharedMem called, size=0x{:X}", size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UnmapSharedMem(Kernel::HLERequestContext& ctx) {
|
void UnmapSharedMem(Kernel::HLERequestContext& ctx) {
|
||||||
|
@ -139,7 +130,7 @@ struct MIC_U::Impl {
|
||||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||||
shared_memory = nullptr;
|
shared_memory = nullptr;
|
||||||
rb.Push(RESULT_SUCCESS);
|
rb.Push(RESULT_SUCCESS);
|
||||||
LOG_TRACE(Service_MIC, "MIC:U UnmapSharedMem called");
|
LOG_TRACE(Service_MIC, "UnmapSharedMem called");
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdateSharedMemBuffer(u64 userdata, s64 cycles_late) {
|
void UpdateSharedMemBuffer(u64 userdata, s64 cycles_late) {
|
||||||
|
@ -152,8 +143,6 @@ struct MIC_U::Impl {
|
||||||
if (!samples.empty()) {
|
if (!samples.empty()) {
|
||||||
// write the samples to sharedmem page
|
// write the samples to sharedmem page
|
||||||
state.WriteSamples(samples);
|
state.WriteSamples(samples);
|
||||||
// write the new offset to the last 4 bytes of the buffer
|
|
||||||
state.UpdateOffset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// schedule next run
|
// schedule next run
|
||||||
|
@ -194,7 +183,7 @@ struct MIC_U::Impl {
|
||||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||||
rb.Push(RESULT_SUCCESS);
|
rb.Push(RESULT_SUCCESS);
|
||||||
LOG_TRACE(Service_MIC,
|
LOG_TRACE(Service_MIC,
|
||||||
"MIC:U StartSampling called, encoding={}, sample_rate={}, "
|
"StartSampling called, encoding={}, sample_rate={}, "
|
||||||
"audio_buffer_offset={}, audio_buffer_size={}, audio_buffer_loop={}",
|
"audio_buffer_offset={}, audio_buffer_size={}, audio_buffer_loop={}",
|
||||||
static_cast<u32>(encoding), static_cast<u32>(sample_rate), audio_buffer_offset,
|
static_cast<u32>(encoding), static_cast<u32>(sample_rate), audio_buffer_offset,
|
||||||
audio_buffer_size, audio_buffer_loop);
|
audio_buffer_size, audio_buffer_loop);
|
||||||
|
@ -207,8 +196,7 @@ struct MIC_U::Impl {
|
||||||
|
|
||||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||||
rb.Push(RESULT_SUCCESS);
|
rb.Push(RESULT_SUCCESS);
|
||||||
LOG_TRACE(Service_MIC, "MIC:U AdjustSampling sample_rate={}",
|
LOG_TRACE(Service_MIC, "AdjustSampling sample_rate={}", static_cast<u32>(sample_rate));
|
||||||
static_cast<u32>(sample_rate));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void StopSampling(Kernel::HLERequestContext& ctx) {
|
void StopSampling(Kernel::HLERequestContext& ctx) {
|
||||||
|
@ -217,7 +205,8 @@ struct MIC_U::Impl {
|
||||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||||
rb.Push(RESULT_SUCCESS);
|
rb.Push(RESULT_SUCCESS);
|
||||||
mic->StopSampling();
|
mic->StopSampling();
|
||||||
LOG_TRACE(Service_MIC, "MIC:U StopSampling called");
|
timing.RemoveEvent(buffer_write_event);
|
||||||
|
LOG_TRACE(Service_MIC, "StopSampling called");
|
||||||
}
|
}
|
||||||
|
|
||||||
void IsSampling(Kernel::HLERequestContext& ctx) {
|
void IsSampling(Kernel::HLERequestContext& ctx) {
|
||||||
|
@ -227,7 +216,7 @@ struct MIC_U::Impl {
|
||||||
rb.Push(RESULT_SUCCESS);
|
rb.Push(RESULT_SUCCESS);
|
||||||
bool is_sampling = mic->IsSampling();
|
bool is_sampling = mic->IsSampling();
|
||||||
rb.Push<bool>(is_sampling);
|
rb.Push<bool>(is_sampling);
|
||||||
LOG_TRACE(Service_MIC, "MIC:U IsSampling: {}", is_sampling);
|
LOG_TRACE(Service_MIC, "IsSampling: {}", is_sampling);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GetBufferFullEvent(Kernel::HLERequestContext& ctx) {
|
void GetBufferFullEvent(Kernel::HLERequestContext& ctx) {
|
||||||
|
@ -246,7 +235,7 @@ struct MIC_U::Impl {
|
||||||
|
|
||||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||||
rb.Push(RESULT_SUCCESS);
|
rb.Push(RESULT_SUCCESS);
|
||||||
LOG_TRACE(Service_MIC, "MIC:U SetGain gain={}", gain);
|
LOG_TRACE(Service_MIC, "SetGain gain={}", gain);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GetGain(Kernel::HLERequestContext& ctx) {
|
void GetGain(Kernel::HLERequestContext& ctx) {
|
||||||
|
@ -256,7 +245,7 @@ struct MIC_U::Impl {
|
||||||
rb.Push(RESULT_SUCCESS);
|
rb.Push(RESULT_SUCCESS);
|
||||||
u8 gain = mic->GetGain();
|
u8 gain = mic->GetGain();
|
||||||
rb.Push<u8>(gain);
|
rb.Push<u8>(gain);
|
||||||
LOG_TRACE(Service_MIC, "MIC:U GetGain gain={}", gain);
|
LOG_TRACE(Service_MIC, "GetGain gain={}", gain);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetPower(Kernel::HLERequestContext& ctx) {
|
void SetPower(Kernel::HLERequestContext& ctx) {
|
||||||
|
@ -266,7 +255,7 @@ struct MIC_U::Impl {
|
||||||
|
|
||||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||||
rb.Push(RESULT_SUCCESS);
|
rb.Push(RESULT_SUCCESS);
|
||||||
LOG_TRACE(Service_MIC, "MIC:U SetPower mic_power={}", power);
|
LOG_TRACE(Service_MIC, "SetPower mic_power={}", power);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GetPower(Kernel::HLERequestContext& ctx) {
|
void GetPower(Kernel::HLERequestContext& ctx) {
|
||||||
|
@ -276,7 +265,7 @@ struct MIC_U::Impl {
|
||||||
rb.Push(RESULT_SUCCESS);
|
rb.Push(RESULT_SUCCESS);
|
||||||
bool mic_power = mic->GetPower();
|
bool mic_power = mic->GetPower();
|
||||||
rb.Push<u8>(mic_power);
|
rb.Push<u8>(mic_power);
|
||||||
LOG_TRACE(Service_MIC, "MIC:U GetPower called");
|
LOG_TRACE(Service_MIC, "GetPower called");
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetIirFilterMic(Kernel::HLERequestContext& ctx) {
|
void SetIirFilterMic(Kernel::HLERequestContext& ctx) {
|
||||||
|
@ -437,3 +426,19 @@ void InstallInterfaces(Core::System& system) {
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Service::MIC
|
} // namespace Service::MIC
|
||||||
|
|
||||||
|
namespace Frontend::Mic {
|
||||||
|
static std::shared_ptr<Mic::Interface> current_mic;
|
||||||
|
|
||||||
|
void RegisterMic(std::shared_ptr<Mic::Interface> mic) {
|
||||||
|
current_mic = mic;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<Mic::Interface> GetCurrentMic() {
|
||||||
|
if (!current_mic) {
|
||||||
|
current_mic = std::make_shared<Mic::NullMic>();
|
||||||
|
}
|
||||||
|
return current_mic;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Frontend::Mic
|
||||||
|
|
|
@ -192,6 +192,4 @@ private:
|
||||||
|
|
||||||
void InstallInterfaces(Core::System& system);
|
void InstallInterfaces(Core::System& system);
|
||||||
|
|
||||||
void ChangeMicImpl();
|
|
||||||
|
|
||||||
} // namespace Service::MIC
|
} // namespace Service::MIC
|
||||||
|
|
|
@ -62,13 +62,13 @@ void Apply() {
|
||||||
}
|
}
|
||||||
// TODO support mic hotswapping by creating the new impl, and copying any parameters to it.
|
// TODO support mic hotswapping by creating the new impl, and copying any parameters to it.
|
||||||
switch (Settings::values.mic_input_type) {
|
switch (Settings::values.mic_input_type) {
|
||||||
case 0:
|
case Settings::MicInputType::None:
|
||||||
Frontend::Mic::RegisterMic(std::make_shared<Frontend::Mic::NullMic>());
|
Frontend::Mic::RegisterMic(std::make_shared<Frontend::Mic::NullMic>());
|
||||||
break;
|
break;
|
||||||
case 1:
|
case Settings::MicInputType::Real:
|
||||||
Frontend::Mic::RegisterMic(std::make_shared<AudioCore::CubebInput>());
|
Frontend::Mic::RegisterMic(std::make_shared<AudioCore::CubebInput>());
|
||||||
break;
|
break;
|
||||||
case 2:
|
case Settings::MicInputType::Static:
|
||||||
Frontend::Mic::RegisterMic(std::make_shared<Frontend::Mic::StaticMic>());
|
Frontend::Mic::RegisterMic(std::make_shared<Frontend::Mic::StaticMic>());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,12 @@ enum class LayoutOption {
|
||||||
SideScreen,
|
SideScreen,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class MicInputType {
|
||||||
|
None,
|
||||||
|
Real,
|
||||||
|
Static,
|
||||||
|
};
|
||||||
|
|
||||||
namespace NativeButton {
|
namespace NativeButton {
|
||||||
enum Values {
|
enum Values {
|
||||||
A,
|
A,
|
||||||
|
@ -167,7 +173,7 @@ struct Values {
|
||||||
bool enable_audio_stretching;
|
bool enable_audio_stretching;
|
||||||
std::string audio_device_id;
|
std::string audio_device_id;
|
||||||
float volume;
|
float volume;
|
||||||
u8 mic_input_type;
|
MicInputType mic_input_type;
|
||||||
std::string mic_input_device;
|
std::string mic_input_device;
|
||||||
|
|
||||||
// Camera
|
// Camera
|
||||||
|
|
Reference in New Issue