From 5118798c30e67e4e79aa7c1d10ad9529cd60714b Mon Sep 17 00:00:00 2001 From: Steveice10 <1269164+Steveice10@users.noreply.github.com> Date: Sun, 12 Nov 2023 13:03:07 -0800 Subject: [PATCH] mic: Refactor microphone state and management. (#7134) --- src/audio_core/cubeb_input.cpp | 82 +++++++++++++++++------------- src/audio_core/cubeb_input.h | 4 +- src/audio_core/input.h | 41 +++------------ src/audio_core/null_input.h | 9 +++- src/audio_core/openal_input.cpp | 26 ++++++---- src/audio_core/openal_input.h | 4 +- src/audio_core/static_input.cpp | 20 -------- src/audio_core/static_input.h | 26 +++++++--- src/core/hle/service/mic/mic_u.cpp | 68 ++++++++++++++----------- src/core/hle/service/mic/mic_u.h | 2 +- 10 files changed, 137 insertions(+), 145 deletions(-) diff --git a/src/audio_core/cubeb_input.cpp b/src/audio_core/cubeb_input.cpp index eccdafecc..baee263fb 100644 --- a/src/audio_core/cubeb_input.cpp +++ b/src/audio_core/cubeb_input.cpp @@ -19,7 +19,7 @@ struct CubebInput::Impl { cubeb* ctx = nullptr; cubeb_stream* stream = nullptr; - std::unique_ptr sample_queue{}; + SampleQueue sample_queue{}; u8 sample_size_in_bytes = 0; static long DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer, @@ -28,38 +28,34 @@ struct CubebInput::Impl { }; CubebInput::CubebInput(std::string device_id) - : impl(std::make_unique()), device_id(std::move(device_id)) { - if (cubeb_init(&impl->ctx, "Citra Input", nullptr) != CUBEB_OK) { - LOG_ERROR(Audio, "cubeb_init failed! Mic will not work properly"); - return; - } - impl->sample_queue = std::make_unique(); -} + : impl(std::make_unique()), device_id(std::move(device_id)) {} CubebInput::~CubebInput() { - if (impl->stream) { - if (cubeb_stream_stop(impl->stream) != CUBEB_OK) { - LOG_ERROR(Audio, "Error stopping cubeb input stream."); - } - cubeb_stream_destroy(impl->stream); - } - - if (impl->ctx) { - cubeb_destroy(impl->ctx); - } + StopSampling(); } void CubebInput::StartSampling(const InputParameters& params) { + if (IsSampling()) { + return; + } + // Cubeb apparently only supports signed 16 bit PCM (and float32 which the 3ds doesn't support) // TODO: Resample the input stream. if (params.sign == Signedness::Unsigned) { - LOG_ERROR(Audio, - "Application requested unsupported unsigned pcm format. Falling back to signed."); + LOG_WARNING( + Audio, + "Application requested unsupported unsigned pcm format. Falling back to signed."); } parameters = params; impl->sample_size_in_bytes = params.sample_size / 8; + auto init_result = cubeb_init(&impl->ctx, "Citra Input", nullptr); + if (init_result != CUBEB_OK) { + LOG_CRITICAL(Audio, "cubeb_init failed: {}", init_result); + return; + } + cubeb_devid input_device = nullptr; if (device_id != auto_device_name && !device_id.empty()) { cubeb_device_collection collection; @@ -87,25 +83,28 @@ void CubebInput::StartSampling(const InputParameters& params) { }; u32 latency_frames = 512; // Firefox default - if (cubeb_get_min_latency(impl->ctx, &input_params, &latency_frames) != CUBEB_OK) { - LOG_WARNING(Audio, "Error getting minimum input latency, falling back to default latency."); + auto latency_result = cubeb_get_min_latency(impl->ctx, &input_params, &latency_frames); + if (latency_result != CUBEB_OK) { + LOG_WARNING( + Audio, "cubeb_get_min_latency failed, falling back to default latency of {} frames: {}", + latency_frames, latency_result); } - if (cubeb_stream_init(impl->ctx, &impl->stream, "Citra Microphone", input_device, &input_params, - nullptr, nullptr, latency_frames, Impl::DataCallback, Impl::StateCallback, - impl.get()) != CUBEB_OK) { - LOG_CRITICAL(Audio, "Error creating cubeb input stream."); + auto stream_init_result = cubeb_stream_init( + impl->ctx, &impl->stream, "Citra Microphone", input_device, &input_params, nullptr, nullptr, + latency_frames, Impl::DataCallback, Impl::StateCallback, impl.get()); + if (stream_init_result != CUBEB_OK) { + LOG_CRITICAL(Audio, "cubeb_stream_init failed: {}", stream_init_result); + StopSampling(); return; } - if (cubeb_stream_start(impl->stream) != CUBEB_OK) { - LOG_CRITICAL(Audio, "Error starting cubeb input stream."); - cubeb_stream_destroy(impl->stream); - impl->stream = nullptr; + auto start_result = cubeb_stream_start(impl->stream); + if (start_result != CUBEB_OK) { + LOG_CRITICAL(Audio, "cubeb_stream_start failed: {}", start_result); + StopSampling(); return; } - - is_sampling = true; } void CubebInput::StopSampling() { @@ -114,11 +113,18 @@ void CubebInput::StopSampling() { cubeb_stream_destroy(impl->stream); impl->stream = nullptr; } - is_sampling = false; + if (impl->ctx) { + cubeb_destroy(impl->ctx); + impl->ctx = nullptr; + } +} + +bool CubebInput::IsSampling() { + return impl->ctx && impl->stream; } void CubebInput::AdjustSampleRate(u32 sample_rate) { - if (!is_sampling) { + if (!IsSampling()) { return; } @@ -129,9 +135,13 @@ void CubebInput::AdjustSampleRate(u32 sample_rate) { } Samples CubebInput::Read() { + if (!IsSampling()) { + return {}; + } + Samples samples{}; Samples queue; - while (impl->sample_queue->Pop(queue)) { + while (impl->sample_queue.Pop(queue)) { samples.insert(samples.end(), queue.begin(), queue.end()); } return samples; @@ -162,7 +172,7 @@ long CubebInput::Impl::DataCallback(cubeb_stream* stream, void* user_data, const const u8* data = reinterpret_cast(input_buffer); samples.insert(samples.begin(), data, data + num_frames * impl->sample_size_in_bytes); } - impl->sample_queue->Push(samples); + impl->sample_queue.Push(samples); // returning less than num_frames here signals cubeb to stop sampling return num_frames; diff --git a/src/audio_core/cubeb_input.h b/src/audio_core/cubeb_input.h index 1bcb803d5..244188f15 100644 --- a/src/audio_core/cubeb_input.h +++ b/src/audio_core/cubeb_input.h @@ -17,11 +17,9 @@ public: ~CubebInput() override; void StartSampling(const InputParameters& params) override; - void StopSampling() override; - + bool IsSampling() override; void AdjustSampleRate(u32 sample_rate) override; - Samples Read() override; private: diff --git a/src/audio_core/input.h b/src/audio_core/input.h index fd3857953..ba2621890 100644 --- a/src/audio_core/input.h +++ b/src/audio_core/input.h @@ -37,12 +37,8 @@ public: /// Stops the microphone. Called by Core virtual void StopSampling() = 0; - /** - * Called from the actual event timing at a constant period under a given sample rate. - * 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; + /// Checks whether the microphone is currently sampling. + virtual bool IsSampling() = 0; /** * Adjusts the Parameters. Implementations should update the parameters field in addition to @@ -50,36 +46,15 @@ public: */ virtual void AdjustSampleRate(u32 sample_rate) = 0; - /// Value from 0 - 100 to adjust the mic gain setting. Called by Core - virtual void SetGain(u8 mic_gain) { - gain = mic_gain; - } - - u8 GetGain() const { - return gain; - } - - void SetPower(bool power) { - powered = power; - } - - bool GetPower() const { - return powered; - } - - bool IsSampling() const { - return is_sampling; - } - - const InputParameters& GetParameters() const { - return parameters; - } + /** + * Called from the actual event timing at a constant period under a given sample rate. + * 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; protected: InputParameters parameters; - u8 gain = 0; - bool is_sampling = false; - bool powered = false; }; } // namespace AudioCore diff --git a/src/audio_core/null_input.h b/src/audio_core/null_input.h index 77cc9a23d..00dacf5bd 100644 --- a/src/audio_core/null_input.h +++ b/src/audio_core/null_input.h @@ -23,13 +23,18 @@ public: is_sampling = false; } - void AdjustSampleRate(u32 sample_rate) override { - parameters.sample_rate = sample_rate; + bool IsSampling() override { + return is_sampling; } + void AdjustSampleRate(u32 sample_rate) override {} + Samples Read() override { return {}; } + +private: + bool is_sampling = false; }; } // namespace AudioCore diff --git a/src/audio_core/openal_input.cpp b/src/audio_core/openal_input.cpp index 79580c375..935b16454 100644 --- a/src/audio_core/openal_input.cpp +++ b/src/audio_core/openal_input.cpp @@ -26,7 +26,7 @@ OpenALInput::~OpenALInput() { } void OpenALInput::StartSampling(const InputParameters& params) { - if (is_sampling) { + if (IsSampling()) { return; } @@ -45,19 +45,20 @@ void OpenALInput::StartSampling(const InputParameters& params) { impl->device = alcCaptureOpenDevice( device_id != auto_device_name && !device_id.empty() ? device_id.c_str() : nullptr, params.sample_rate, format, static_cast(params.buffer_size)); - if (!impl->device) { - LOG_CRITICAL(Audio, "alcCaptureOpenDevice failed."); + auto open_error = alcGetError(impl->device); + if (impl->device == nullptr || open_error != ALC_NO_ERROR) { + LOG_CRITICAL(Audio, "alcCaptureOpenDevice failed: {}", open_error); + StopSampling(); return; } alcCaptureStart(impl->device); - auto error = alcGetError(impl->device); - if (error != ALC_NO_ERROR) { - LOG_CRITICAL(Audio, "alcCaptureStart failed: {}", error); + auto capture_error = alcGetError(impl->device); + if (capture_error != ALC_NO_ERROR) { + LOG_CRITICAL(Audio, "alcCaptureStart failed: {}", capture_error); + StopSampling(); return; } - - is_sampling = true; } void OpenALInput::StopSampling() { @@ -66,11 +67,14 @@ void OpenALInput::StopSampling() { alcCaptureCloseDevice(impl->device); impl->device = nullptr; } - is_sampling = false; +} + +bool OpenALInput::IsSampling() { + return impl->device != nullptr; } void OpenALInput::AdjustSampleRate(u32 sample_rate) { - if (!is_sampling) { + if (!IsSampling()) { return; } @@ -81,7 +85,7 @@ void OpenALInput::AdjustSampleRate(u32 sample_rate) { } Samples OpenALInput::Read() { - if (!is_sampling) { + if (!IsSampling()) { return {}; } diff --git a/src/audio_core/openal_input.h b/src/audio_core/openal_input.h index 8fb2442d8..874dc0b88 100644 --- a/src/audio_core/openal_input.h +++ b/src/audio_core/openal_input.h @@ -17,11 +17,9 @@ public: ~OpenALInput() override; void StartSampling(const InputParameters& params) override; - void StopSampling() override; - + bool IsSampling() override; void AdjustSampleRate(u32 sample_rate) override; - Samples Read() override; private: diff --git a/src/audio_core/static_input.cpp b/src/audio_core/static_input.cpp index 61d367820..40bee9080 100644 --- a/src/audio_core/static_input.cpp +++ b/src/audio_core/static_input.cpp @@ -19,24 +19,4 @@ StaticInput::StaticInput() : CACHE_8_BIT{NOISE_SAMPLE_8_BIT.begin(), NOISE_SAMPLE_8_BIT.end()}, CACHE_16_BIT{NOISE_SAMPLE_16_BIT.begin(), NOISE_SAMPLE_16_BIT.end()} {} -StaticInput::~StaticInput() = default; - -void StaticInput::StartSampling(const InputParameters& params) { - sample_rate = params.sample_rate; - sample_size = params.sample_size; - - parameters = params; - is_sampling = true; -} - -void StaticInput::StopSampling() { - is_sampling = false; -} - -void StaticInput::AdjustSampleRate(u32 sample_rate) {} - -Samples StaticInput::Read() { - return (sample_size == 8) ? CACHE_8_BIT : CACHE_16_BIT; -} - } // namespace AudioCore diff --git a/src/audio_core/static_input.h b/src/audio_core/static_input.h index 4c78311bf..9996d2a6f 100644 --- a/src/audio_core/static_input.h +++ b/src/audio_core/static_input.h @@ -15,17 +15,29 @@ namespace AudioCore { class StaticInput final : public Input { public: StaticInput(); - ~StaticInput() override; + ~StaticInput() = default; - void StartSampling(const InputParameters& params) override; - void StopSampling() override; - void AdjustSampleRate(u32 sample_rate) override; + void StartSampling(const InputParameters& params) { + parameters = params; + is_sampling = true; + } - Samples Read() override; + void StopSampling() { + is_sampling = false; + } + + bool IsSampling() { + return is_sampling; + } + + void AdjustSampleRate(u32 sample_rate) {} + + Samples Read() { + return (parameters.sample_size == 8) ? CACHE_8_BIT : CACHE_16_BIT; + } private: - u16 sample_rate = 0; - u8 sample_size = 0; + bool is_sampling = false; std::vector CACHE_8_BIT; std::vector CACHE_16_BIT; }; diff --git a/src/core/hle/service/mic/mic_u.cpp b/src/core/hle/service/mic/mic_u.cpp index 9b24f4591..a5816400b 100644 --- a/src/core/hle/service/mic/mic_u.cpp +++ b/src/core/hle/service/mic/mic_u.cpp @@ -76,6 +76,8 @@ struct State { u32 initial_offset = 0; bool looped_buffer = false; u8 sample_size = 0; + u8 gain = 0; + bool power = false; SampleRate sample_rate = SampleRate::Rate16360; void WriteSamples(std::span samples) { @@ -124,6 +126,8 @@ private: ar& initial_offset; ar& looped_buffer; ar& sample_size; + ar& gain; + ar& power; ar& sample_rate; sharedmem_buffer = _memory_ref ? _memory_ref->GetPointer() : nullptr; } @@ -167,13 +171,14 @@ struct MIC_U::Impl { } void UpdateSharedMemBuffer(std::uintptr_t user_data, s64 cycles_late) { + // If the event was scheduled before the application requested the mic to stop sampling + if (!mic || !mic->IsSampling()) { + return; + } + if (change_mic_impl_requested.exchange(false)) { CreateMic(); } - // If the event was scheduled before the application requested the mic to stop sampling - if (!mic->IsSampling()) { - return; - } AudioCore::Samples samples = mic->Read(); if (!samples.empty()) { @@ -204,10 +209,11 @@ struct MIC_U::Impl { u32 audio_buffer_size = rp.Pop(); bool audio_buffer_loop = rp.Pop(); - if (mic->IsSampling()) { + if (mic && mic->IsSampling()) { LOG_CRITICAL(Service_MIC, "Application started sampling again before stopping sampling"); mic->StopSampling(); + mic.reset(); } u8 sample_size = encoding == Encoding::PCM8Signed || encoding == Encoding::PCM8 ? 8 : 16; @@ -218,6 +224,7 @@ struct MIC_U::Impl { state.looped_buffer = audio_buffer_loop; state.size = audio_buffer_size; + CreateMic(); StartSampling(); timing.ScheduleEvent(GetBufferUpdatePeriod(state.sample_rate), buffer_write_event); @@ -233,7 +240,10 @@ struct MIC_U::Impl { void AdjustSampling(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); SampleRate sample_rate = rp.PopEnum(); - mic->AdjustSampleRate(GetSampleRateInHz(sample_rate)); + state.sample_rate = sample_rate; + if (mic) { + mic->AdjustSampleRate(GetSampleRateInHz(sample_rate)); + } IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); @@ -245,8 +255,11 @@ struct MIC_U::Impl { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); - mic->StopSampling(); timing.RemoveEvent(buffer_write_event); + if (mic) { + mic->StopSampling(); + mic.reset(); + } LOG_TRACE(Service_MIC, "called"); } @@ -255,7 +268,7 @@ struct MIC_U::Impl { IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(RESULT_SUCCESS); - bool is_sampling = mic->IsSampling(); + bool is_sampling = mic && mic->IsSampling(); rb.Push(is_sampling); LOG_TRACE(Service_MIC, "IsSampling: {}", is_sampling); } @@ -272,7 +285,7 @@ struct MIC_U::Impl { void SetGain(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); u8 gain = rp.Pop(); - mic->SetGain(gain); + state.gain = gain; IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); @@ -284,15 +297,14 @@ struct MIC_U::Impl { IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(RESULT_SUCCESS); - u8 gain = mic->GetGain(); - rb.Push(gain); + rb.Push(state.gain); LOG_TRACE(Service_MIC, "gain={}", gain); } void SetPower(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); bool power = rp.Pop(); - mic->SetPower(power); + state.power = power; IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); @@ -304,8 +316,7 @@ struct MIC_U::Impl { IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(RESULT_SUCCESS); - bool mic_power = mic->GetPower(); - rb.Push(mic_power); + rb.Push(state.power); LOG_TRACE(Service_MIC, "called"); } @@ -358,21 +369,18 @@ struct MIC_U::Impl { } void CreateMic() { - std::unique_ptr new_mic = AudioCore::CreateInputFromID( - Settings::values.input_type.GetValue(), Settings::values.input_device.GetValue()); - - // If theres already a mic, copy over any data to the new mic impl - if (mic) { - new_mic->SetGain(mic->GetGain()); - new_mic->SetPower(mic->GetPower()); - auto params = mic->GetParameters(); - if (mic->IsSampling()) { - mic->StopSampling(); - new_mic->StartSampling(params); - } + const auto was_sampling = mic && mic->IsSampling(); + if (was_sampling) { + mic->StopSampling(); + mic.reset(); + } + + mic = AudioCore::CreateInputFromID(Settings::values.input_type.GetValue(), + Settings::values.input_device.GetValue()); + if (was_sampling) { + StartSampling(); } - mic = std::move(new_mic); change_mic_impl_requested.store(false); } @@ -503,12 +511,14 @@ MIC_U::MIC_U(Core::System& system) // clang-format on }; - impl->CreateMic(); RegisterHandlers(functions); } MIC_U::~MIC_U() { - impl->mic->StopSampling(); + if (impl->mic) { + impl->mic->StopSampling(); + impl->mic.reset(); + } } void MIC_U::ReloadMic() { diff --git a/src/core/hle/service/mic/mic_u.h b/src/core/hle/service/mic/mic_u.h index 1a404f194..96aa5437e 100644 --- a/src/core/hle/service/mic/mic_u.h +++ b/src/core/hle/service/mic/mic_u.h @@ -17,7 +17,7 @@ namespace Service::MIC { class MIC_U final : public ServiceFramework { public: explicit MIC_U(Core::System& system); - ~MIC_U(); + ~MIC_U() override; void ReloadMic();