audio_core: Implement OpenAL backend (#6450)
parent
ce553ab995
commit
055a58f01e
@ -0,0 +1 @@
|
||||
Subproject commit d9fed51aa6391debc31dbbca550f055c980afe70
|
@ -1,38 +0,0 @@
|
||||
// Copyright 2020 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <jni.h>
|
||||
#include "common/logging/log.h"
|
||||
#include "jni/id_cache.h"
|
||||
#include "jni/mic.h"
|
||||
|
||||
#ifdef HAVE_CUBEB
|
||||
#include "audio_core/cubeb_input.h"
|
||||
#endif
|
||||
|
||||
namespace Mic {
|
||||
|
||||
AndroidFactory::~AndroidFactory() = default;
|
||||
|
||||
std::unique_ptr<Frontend::Mic::Interface> AndroidFactory::Create(std::string mic_device_name) {
|
||||
#ifdef HAVE_CUBEB
|
||||
if (!permission_granted) {
|
||||
JNIEnv* env = IDCache::GetEnvForThread();
|
||||
permission_granted = env->CallStaticBooleanMethod(IDCache::GetNativeLibraryClass(),
|
||||
IDCache::GetRequestMicPermission());
|
||||
}
|
||||
|
||||
if (permission_granted) {
|
||||
return std::make_unique<AudioCore::CubebInput>(std::move(mic_device_name));
|
||||
} else {
|
||||
LOG_WARNING(Frontend, "Mic permissions denied");
|
||||
return std::make_unique<Frontend::Mic::NullMic>();
|
||||
}
|
||||
#else
|
||||
LOG_WARNING(Frontend, "No cubeb support");
|
||||
return std::make_unique<Frontend::Mic::NullMic>();
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace Mic
|
@ -1,21 +0,0 @@
|
||||
// Copyright 2020 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/frontend/mic.h"
|
||||
|
||||
namespace Mic {
|
||||
|
||||
class AndroidFactory final : public Frontend::Mic::RealMicFactory {
|
||||
public:
|
||||
~AndroidFactory() override;
|
||||
|
||||
std::unique_ptr<Frontend::Mic::Interface> Create(std::string mic_device_name) override;
|
||||
|
||||
private:
|
||||
bool permission_granted = false;
|
||||
};
|
||||
|
||||
} // namespace Mic
|
@ -0,0 +1,85 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
enum class Signedness : u8 {
|
||||
Signed,
|
||||
Unsigned,
|
||||
};
|
||||
|
||||
using Samples = std::vector<u8>;
|
||||
|
||||
struct InputParameters {
|
||||
Signedness sign;
|
||||
u8 sample_size;
|
||||
bool buffer_loop;
|
||||
u32 sample_rate;
|
||||
u32 buffer_offset;
|
||||
u32 buffer_size;
|
||||
};
|
||||
|
||||
class Input {
|
||||
public:
|
||||
Input() = default;
|
||||
|
||||
virtual ~Input() = default;
|
||||
|
||||
/// Starts the microphone. Called by Core
|
||||
virtual void StartSampling(const InputParameters& params) = 0;
|
||||
|
||||
/// 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;
|
||||
|
||||
/**
|
||||
* Adjusts the Parameters. Implementations should update the parameters field in addition to
|
||||
* changing the mic to sample according to the new parameters. Called by Core
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
protected:
|
||||
InputParameters parameters;
|
||||
u8 gain = 0;
|
||||
bool is_sampling = false;
|
||||
bool powered = false;
|
||||
};
|
||||
|
||||
} // namespace AudioCore
|
@ -0,0 +1,108 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "audio_core/input_details.h"
|
||||
#include "audio_core/null_input.h"
|
||||
#include "audio_core/static_input.h"
|
||||
#ifdef HAVE_CUBEB
|
||||
#include "audio_core/cubeb_input.h"
|
||||
#endif
|
||||
#ifdef HAVE_OPENAL
|
||||
#include "audio_core/openal_input.h"
|
||||
#endif
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
|
||||
namespace AudioCore {
|
||||
namespace {
|
||||
struct InputDetails {
|
||||
using FactoryFn = std::unique_ptr<Input> (*)(std::string_view);
|
||||
using ListDevicesFn = std::vector<std::string> (*)();
|
||||
|
||||
/// Type of this input.
|
||||
InputType type;
|
||||
/// Name for this input.
|
||||
std::string_view name;
|
||||
/// A method to call to construct an instance of this type of input.
|
||||
FactoryFn factory;
|
||||
/// A method to call to list available devices.
|
||||
ListDevicesFn list_devices;
|
||||
};
|
||||
|
||||
// input_details is ordered in terms of desirability, with the best choice at the top.
|
||||
constexpr std::array input_details = {
|
||||
#ifdef HAVE_CUBEB
|
||||
InputDetails{InputType::Cubeb, "Real Device (Cubeb)",
|
||||
[](std::string_view device_id) -> std::unique_ptr<Input> {
|
||||
if (!Core::System::GetInstance().HasMicPermission()) {
|
||||
LOG_WARNING(Audio,
|
||||
"Microphone permission denied, falling back to null input.");
|
||||
return std::make_unique<NullInput>();
|
||||
}
|
||||
return std::make_unique<CubebInput>(std::string(device_id));
|
||||
},
|
||||
&ListCubebInputDevices},
|
||||
#endif
|
||||
#ifdef HAVE_OPENAL
|
||||
InputDetails{InputType::OpenAL, "Real Device (OpenAL)",
|
||||
[](std::string_view device_id) -> std::unique_ptr<Input> {
|
||||
if (!Core::System::GetInstance().HasMicPermission()) {
|
||||
LOG_WARNING(Audio,
|
||||
"Microphone permission denied, falling back to null input.");
|
||||
return std::make_unique<NullInput>();
|
||||
}
|
||||
return std::make_unique<OpenALInput>(std::string(device_id));
|
||||
},
|
||||
&ListOpenALInputDevices},
|
||||
#endif
|
||||
InputDetails{InputType::Static, "Static Noise",
|
||||
[](std::string_view device_id) -> std::unique_ptr<Input> {
|
||||
return std::make_unique<StaticInput>();
|
||||
},
|
||||
[] { return std::vector<std::string>{"Static Noise"}; }},
|
||||
InputDetails{InputType::Null, "None",
|
||||
[](std::string_view device_id) -> std::unique_ptr<Input> {
|
||||
return std::make_unique<NullInput>();
|
||||
},
|
||||
[] { return std::vector<std::string>{"None"}; }},
|
||||
};
|
||||
|
||||
const InputDetails& GetInputDetails(InputType input_type) {
|
||||
auto iter = std::find_if(
|
||||
input_details.begin(), input_details.end(),
|
||||
[input_type](const auto& input_detail) { return input_detail.type == input_type; });
|
||||
|
||||
if (input_type == InputType::Auto || iter == input_details.end()) {
|
||||
if (input_type != InputType::Auto) {
|
||||
LOG_ERROR(Audio, "AudioCore::GetInputDetails given invalid input_type {}", input_type);
|
||||
}
|
||||
// Auto-select.
|
||||
// input_details is ordered in terms of desirability, with the best choice at the front.
|
||||
iter = input_details.begin();
|
||||
}
|
||||
|
||||
return *iter;
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
std::string_view GetInputName(InputType input_type) {
|
||||
if (input_type == InputType::Auto) {
|
||||
return "Auto";
|
||||
}
|
||||
return GetInputDetails(input_type).name;
|
||||
}
|
||||
|
||||
std::vector<std::string> GetDeviceListForInput(InputType input_type) {
|
||||
return GetInputDetails(input_type).list_devices();
|
||||
}
|
||||
|
||||
std::unique_ptr<Input> CreateInputFromID(InputType input_type, std::string_view device_id) {
|
||||
return GetInputDetails(input_type).factory(device_id);
|
||||
}
|
||||
|
||||
} // namespace AudioCore
|
@ -0,0 +1,36 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
class Input;
|
||||
|
||||
enum class InputType : u32 {
|
||||
Auto = 0,
|
||||
Null = 1,
|
||||
Static = 2,
|
||||
Cubeb = 3,
|
||||
OpenAL = 4,
|
||||
|
||||
NumInputTypes,
|
||||
};
|
||||
|
||||
/// Gets the name of a input type.
|
||||
std::string_view GetInputName(InputType input_type);
|
||||
|
||||
/// Gets the list of devices for a particular input identified by the given ID.
|
||||
std::vector<std::string> GetDeviceListForInput(InputType input_type);
|
||||
|
||||
/// Creates an audio input identified by the given device ID.
|
||||
std::unique_ptr<Input> CreateInputFromID(InputType input_type, std::string_view device_id);
|
||||
|
||||
} // namespace AudioCore
|
@ -0,0 +1,35 @@
|
||||
// Copyright 2019 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include "audio_core/input.h"
|
||||
#include "common/swap.h"
|
||||
#include "common/threadsafe_queue.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
class NullInput final : public Input {
|
||||
public:
|
||||
void StartSampling(const InputParameters& params) override {
|
||||
parameters = params;
|
||||
is_sampling = true;
|
||||
}
|
||||
|
||||
void StopSampling() override {
|
||||
is_sampling = false;
|
||||
}
|
||||
|
||||
void AdjustSampleRate(u32 sample_rate) override {
|
||||
parameters.sample_rate = sample_rate;
|
||||
}
|
||||
|
||||
Samples Read() override {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace AudioCore
|
@ -0,0 +1,133 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <AL/al.h>
|
||||
#include <AL/alc.h>
|
||||
#include "audio_core/input.h"
|
||||
#include "audio_core/openal_input.h"
|
||||
#include "audio_core/sink.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
struct OpenALInput::Impl {
|
||||
ALCdevice* device = nullptr;
|
||||
u8 sample_size_in_bytes = 0;
|
||||
};
|
||||
|
||||
OpenALInput::OpenALInput(std::string device_id)
|
||||
: impl(std::make_unique<Impl>()), device_id(std::move(device_id)) {}
|
||||
|
||||
OpenALInput::~OpenALInput() {
|
||||
StopSampling();
|
||||
}
|
||||
|
||||
void OpenALInput::StartSampling(const InputParameters& params) {
|
||||
if (is_sampling) {
|
||||
return;
|
||||
}
|
||||
|
||||
// OpenAL supports unsigned 8-bit and signed 16-bit PCM.
|
||||
// TODO: Re-sample the stream.
|
||||
if ((params.sample_size == 8 && params.sign == Signedness::Signed) ||
|
||||
(params.sample_size == 16 && params.sign == Signedness::Unsigned)) {
|
||||
LOG_WARNING(Audio, "Application requested unsupported unsigned PCM format. Falling back to "
|
||||
"supported format.");
|
||||
}
|
||||
|
||||
parameters = params;
|
||||
impl->sample_size_in_bytes = params.sample_size / 8;
|
||||
|
||||
auto format = params.sample_size == 16 ? AL_FORMAT_MONO16 : AL_FORMAT_MONO8;
|
||||
impl->device = alcCaptureOpenDevice(
|
||||
device_id != auto_device_name && !device_id.empty() ? device_id.c_str() : nullptr,
|
||||
params.sample_rate, format, static_cast<ALsizei>(params.buffer_size));
|
||||
if (!impl->device) {
|
||||
LOG_CRITICAL(Audio, "alcCaptureOpenDevice failed.");
|
||||
return;
|
||||
}
|
||||
|
||||
alcCaptureStart(impl->device);
|
||||
auto error = alcGetError(impl->device);
|
||||
if (error != ALC_NO_ERROR) {
|
||||
LOG_CRITICAL(Audio, "alcCaptureStart failed: {}", error);
|
||||
return;
|
||||
}
|
||||
|
||||
is_sampling = true;
|
||||
}
|
||||
|
||||
void OpenALInput::StopSampling() {
|
||||
if (impl->device) {
|
||||
alcCaptureStop(impl->device);
|
||||
alcCaptureCloseDevice(impl->device);
|
||||
impl->device = nullptr;
|
||||
}
|
||||
is_sampling = false;
|
||||
}
|
||||
|
||||
void OpenALInput::AdjustSampleRate(u32 sample_rate) {
|
||||
if (!is_sampling) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto new_params = parameters;
|
||||
new_params.sample_rate = sample_rate;
|
||||
StopSampling();
|
||||
StartSampling(new_params);
|
||||
}
|
||||
|
||||
Samples OpenALInput::Read() {
|
||||
if (!is_sampling) {
|
||||
return {};
|
||||
}
|
||||
|
||||
ALCint samples_captured = 0;
|
||||
alcGetIntegerv(impl->device, ALC_CAPTURE_SAMPLES, 1, &samples_captured);
|
||||
auto error = alcGetError(impl->device);
|
||||
if (error != ALC_NO_ERROR) {
|
||||
LOG_WARNING(Audio, "alcGetIntegerv(ALC_CAPTURE_SAMPLES) failed: {}", error);
|
||||
return {};
|
||||
}
|
||||
|
||||
auto num_samples = std::min(samples_captured, static_cast<ALsizei>(parameters.buffer_size /
|
||||
impl->sample_size_in_bytes));
|
||||
Samples samples(num_samples * impl->sample_size_in_bytes);
|
||||
|
||||
alcCaptureSamples(impl->device, samples.data(), num_samples);
|
||||
error = alcGetError(impl->device);
|
||||
if (error != ALC_NO_ERROR) {
|
||||
LOG_WARNING(Audio, "alcCaptureSamples failed: {}", error);
|
||||
return {};
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
std::vector<std::string> ListOpenALInputDevices() {
|
||||
const char* devices_str;
|
||||
if (alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT") != AL_FALSE) {
|
||||
devices_str = alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER);
|
||||
} else {
|
||||
LOG_WARNING(
|
||||
Audio,
|
||||
"Missing OpenAL device enumeration extensions, cannot list audio capture devices.");
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!devices_str || *devices_str == '\0') {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<std::string> device_list;
|
||||
while (*devices_str != '\0') {
|
||||
device_list.emplace_back(devices_str);
|
||||
devices_str += strlen(devices_str) + 1;
|
||||
}
|
||||
return device_list;
|
||||
}
|
||||
|
||||
} // namespace AudioCore
|
@ -0,0 +1,35 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "audio_core/input.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
class OpenALInput final : public Input {
|
||||
public:
|
||||
explicit OpenALInput(std::string device_id);
|
||||
~OpenALInput() override;
|
||||
|
||||
void StartSampling(const InputParameters& params) override;
|
||||
|
||||
void StopSampling() override;
|
||||
|
||||
void AdjustSampleRate(u32 sample_rate) override;
|
||||
|
||||
Samples Read() override;
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl;
|
||||
std::string device_id;
|
||||
};
|
||||
|
||||
std::vector<std::string> ListOpenALInputDevices();
|
||||
|
||||
} // namespace AudioCore
|
@ -0,0 +1,176 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <vector>
|
||||
#include <AL/al.h>
|
||||
#include <AL/alc.h>
|
||||
#include <AL/alext.h>
|
||||
#include "audio_core/audio_types.h"
|
||||
#include "audio_core/openal_sink.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
struct OpenALSink::Impl {
|
||||
unsigned int sample_rate = 0;
|
||||
|
||||
ALCdevice* device = nullptr;
|
||||
ALCcontext* context = nullptr;
|
||||
ALuint buffer = 0;
|
||||
ALuint source = 0;
|
||||
|
||||
std::function<void(s16*, std::size_t)> cb;
|
||||
|
||||
static ALsizei Callback(void* impl_, void* buffer, ALsizei buffer_size_in_bytes);
|
||||
};
|
||||
|
||||
OpenALSink::OpenALSink(std::string device_name) : impl(std::make_unique<Impl>()) {
|
||||
impl->device = alcOpenDevice(
|
||||
device_name != auto_device_name && !device_name.empty() ? device_name.c_str() : nullptr);
|
||||
if (!impl->device) {
|
||||
LOG_CRITICAL(Audio_Sink, "alcOpenDevice failed.");
|
||||
return;
|
||||
}
|
||||
|
||||
impl->context = alcCreateContext(impl->device, nullptr);
|
||||
if (impl->context == nullptr) {
|
||||
LOG_CRITICAL(Audio_Sink, "alcCreateContext failed: {}", alcGetError(impl->device));
|
||||
alcCloseDevice(impl->device);
|
||||
return;
|
||||
}
|
||||
|
||||
if (alcMakeContextCurrent(impl->context) == ALC_FALSE) {
|
||||
LOG_CRITICAL(Audio_Sink, "alcMakeContextCurrent failed: {}", alcGetError(impl->device));
|
||||
alcDestroyContext(impl->context);
|
||||
alcCloseDevice(impl->device);
|
||||
return;
|
||||
}
|
||||
|
||||
if (alIsExtensionPresent("AL_SOFT_callback_buffer") == AL_FALSE) {
|
||||
if (alGetError() != AL_NO_ERROR) {
|
||||
LOG_CRITICAL(Audio_Sink, "alIsExtensionPresent failed: {}", alGetError());
|
||||
} else {
|
||||
LOG_CRITICAL(Audio_Sink, "Missing required extension AL_SOFT_callback_buffer.");
|
||||
}
|
||||
alcDestroyContext(impl->context);
|
||||
alcCloseDevice(impl->device);
|
||||
return;
|
||||
}
|
||||
|
||||
alGenBuffers(1, &impl->buffer);
|
||||
if (alGetError() != AL_NO_ERROR) {
|
||||
LOG_CRITICAL(Audio_Sink, "alGetError failed: {}", alGetError());
|
||||
alcDestroyContext(impl->context);
|
||||
alcCloseDevice(impl->device);
|
||||
return;
|
||||
}
|
||||
|
||||
alGenSources(1, &impl->source);
|
||||
if (alGetError() != AL_NO_ERROR) {
|
||||
LOG_CRITICAL(Audio_Sink, "alGenSources failed: {}", alGetError());
|
||||
alDeleteBuffers(1, &impl->buffer);
|
||||
alcDestroyContext(impl->context);
|
||||
alcCloseDevice(impl->device);
|
||||
return;
|
||||
}
|
||||
|
||||
auto alBufferCallbackSOFT =
|
||||
reinterpret_cast<LPALBUFFERCALLBACKSOFT>(alGetProcAddress("alBufferCallbackSOFT"));
|
||||
alBufferCallbackSOFT(impl->buffer, AL_FORMAT_STEREO16, native_sample_rate, &Impl::Callback,
|
||||
impl.get());
|
||||
if (alGetError() != AL_NO_ERROR) {
|
||||
LOG_CRITICAL(Audio_Sink, "alBufferCallbackSOFT failed: {}", alGetError());
|
||||
alDeleteSources(1, &impl->source);
|
||||
alDeleteBuffers(1, &impl->buffer);
|
||||
alcDestroyContext(impl->context);
|
||||
alcCloseDevice(impl->device);
|
||||
return;
|
||||
}
|
||||
|
||||
alSourcei(impl->source, AL_BUFFER, static_cast<ALint>(impl->buffer));
|
||||
if (alGetError() != AL_NO_ERROR) {
|
||||
LOG_CRITICAL(Audio_Sink, "alSourcei failed: {}", alGetError());
|
||||
alDeleteSources(1, &impl->source);
|
||||
alDeleteBuffers(1, &impl->buffer);
|
||||
alcDestroyContext(impl->context);
|
||||
alcCloseDevice(impl->device);
|
||||
return;
|
||||
}
|
||||
|
||||
alSourcePlay(impl->source);
|
||||
if (alGetError() != AL_NO_ERROR) {
|
||||
LOG_CRITICAL(Audio_Sink, "alSourcePlay failed: {}", alGetError());
|
||||
alDeleteSources(1, &impl->source);
|
||||
alDeleteBuffers(1, &impl->buffer);
|
||||
alcDestroyContext(impl->context);
|
||||
alcCloseDevice(impl->device);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
OpenALSink::~OpenALSink() {
|
||||
if (impl->source) {
|
||||
alSourceStop(impl->source);
|
||||
alDeleteSources(1, &impl->source);
|
||||
impl->source = 0;
|
||||
}
|
||||
if (impl->buffer) {
|
||||
alDeleteBuffers(1, &impl->buffer);
|
||||
impl->buffer = 0;
|
||||
}
|
||||
if (impl->context) {
|
||||
alcDestroyContext(impl->context);
|
||||
impl->context = nullptr;
|
||||
}
|
||||
if (impl->device) {
|
||||
alcCloseDevice(impl->device);
|
||||
impl->device = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int OpenALSink::GetNativeSampleRate() const {
|
||||
return native_sample_rate;
|
||||
}
|
||||
|
||||
void OpenALSink::SetCallback(std::function<void(s16*, std::size_t)> cb) {
|
||||
impl->cb = cb;
|
||||
}
|
||||
|
||||
ALsizei OpenALSink::Impl::Callback(void* impl_, void* buffer, ALsizei buffer_size_in_bytes) {
|
||||
auto impl = reinterpret_cast<Impl*>(impl_);
|
||||
if (!impl || !impl->cb) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const size_t num_frames = buffer_size_in_bytes / (2 * sizeof(s16));
|
||||
impl->cb(reinterpret_cast<s16*>(buffer), num_frames);
|
||||
|
||||
return buffer_size_in_bytes;
|
||||
}
|
||||
|
||||
std::vector<std::string> ListOpenALSinkDevices() {
|
||||
const char* devices_str;
|
||||
if (alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT") != AL_FALSE) {
|
||||
devices_str = alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER);
|
||||
} else if (alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT") != AL_FALSE) {
|
||||
devices_str = alcGetString(nullptr, ALC_DEVICE_SPECIFIER);
|
||||
} else {
|
||||
LOG_WARNING(Audio_Sink,
|
||||
"Missing OpenAL device enumeration extensions, cannot list audio devices.");
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!devices_str || *devices_str == '\0') {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<std::string> device_list;
|
||||
while (*devices_str != '\0') {
|
||||
device_list.emplace_back(devices_str);
|
||||
devices_str += strlen(devices_str) + 1;
|
||||
}
|
||||
return device_list;
|
||||
}
|
||||
|
||||
} // namespace AudioCore
|
@ -0,0 +1,30 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "audio_core/sink.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
class OpenALSink final : public Sink {
|
||||
public:
|
||||
explicit OpenALSink(std::string device_id);
|
||||
~OpenALSink() override;
|
||||
|
||||
unsigned int GetNativeSampleRate() const override;
|
||||
|
||||
void SetCallback(std::function<void(s16*, std::size_t)> cb) override;
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl;
|
||||
};
|
||||
|
||||
std::vector<std::string> ListOpenALSinkDevices();
|
||||
|
||||
} // namespace AudioCore
|
@ -0,0 +1,42 @@
|
||||
// Copyright 2019 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <array>
|
||||
#include "audio_core/input.h"
|
||||
#include "audio_core/static_input.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
constexpr std::array<u8, 16> NOISE_SAMPLE_8_BIT = {0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xF5, 0xFF, 0xFF, 0xFF, 0xFF, 0x8E, 0xFF};
|
||||
|
||||
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,
|
||||
0x28, 0x0F, 0x6B, 0xEB, 0x1C, 0xC0, 0xCB, 0x9D, 0x46, 0x90, 0xDF, 0x98, 0xEA, 0xAE, 0xB5, 0xC4};
|
||||
|
||||
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
|
@ -0,0 +1,33 @@
|
||||
// Copyright 2019 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include "audio_core/input.h"
|
||||
#include "common/swap.h"
|
||||
#include "common/threadsafe_queue.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
class StaticInput final : public Input {
|
||||
public:
|
||||
StaticInput();
|
||||
~StaticInput() override;
|
||||
|
||||
void StartSampling(const InputParameters& params) override;
|
||||
void StopSampling() override;
|
||||
void AdjustSampleRate(u32 sample_rate) override;
|
||||
|
||||
Samples Read() override;
|
||||
|
||||
private:
|
||||
u16 sample_rate = 0;
|
||||
u8 sample_size = 0;
|
||||
std::vector<u8> CACHE_8_BIT;
|
||||
std::vector<u8> CACHE_16_BIT;
|
||||
};
|
||||
|
||||
} // namespace AudioCore
|
@ -1,86 +0,0 @@
|
||||
// Copyright 2019 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <array>
|
||||
#include "core/frontend/mic.h"
|
||||
|
||||
#ifdef HAVE_CUBEB
|
||||
#include "audio_core/cubeb_input.h"
|
||||
#endif
|
||||
|
||||
namespace Frontend::Mic {
|
||||
|
||||
constexpr std::array<u8, 16> NOISE_SAMPLE_8_BIT = {0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xF5, 0xFF, 0xFF, 0xFF, 0xFF, 0x8E, 0xFF};
|
||||
|
||||
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,
|
||||
0x28, 0x0F, 0x6B, 0xEB, 0x1C, 0xC0, 0xCB, 0x9D, 0x46, 0x90, 0xDF, 0x98, 0xEA, 0xAE, 0xB5, 0xC4};
|
||||
|
||||
Interface::~Interface() = default;
|
||||
|
||||
void NullMic::StartSampling(const Parameters& params) {
|
||||
parameters = params;
|
||||
is_sampling = true;
|
||||
}
|
||||
|
||||
void NullMic::StopSampling() {
|
||||
is_sampling = false;
|
||||
}
|
||||
|
||||
void NullMic::AdjustSampleRate(u32 sample_rate) {
|
||||
parameters.sample_rate = sample_rate;
|
||||
}
|
||||
|
||||
Samples NullMic::Read() {
|
||||
return {};
|
||||
}
|
||||
|
||||
StaticMic::StaticMic()
|
||||
: 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()} {}
|
||||
|
||||
StaticMic::~StaticMic() = default;
|
||||
|
||||
void StaticMic::StartSampling(const Parameters& params) {
|
||||
sample_rate = params.sample_rate;
|
||||
sample_size = params.sample_size;
|
||||
|
||||
parameters = params;
|
||||
is_sampling = true;
|
||||
}
|
||||
|
||||
void StaticMic::StopSampling() {
|
||||
is_sampling = false;
|
||||
}
|
||||
|
||||
void StaticMic::AdjustSampleRate(u32 sample_rate) {}
|
||||
|
||||
Samples StaticMic::Read() {
|
||||
return (sample_size == 8) ? CACHE_8_BIT : CACHE_16_BIT;
|
||||
}
|
||||
|
||||
RealMicFactory::~RealMicFactory() = default;
|
||||
|
||||
NullFactory::~NullFactory() = default;
|
||||
|
||||
std::unique_ptr<Interface> NullFactory::Create([[maybe_unused]] std::string mic_device_name) {
|
||||
return std::make_unique<NullMic>();
|
||||
}
|
||||
|
||||
#ifdef HAVE_CUBEB
|
||||
static std::unique_ptr<RealMicFactory> g_factory = std::make_unique<AudioCore::CubebFactory>();
|
||||
#else
|
||||
static std::unique_ptr<RealMicFactory> g_factory = std::make_unique<NullFactory>();
|
||||
#endif
|
||||
|
||||
void RegisterRealMicFactory(std::unique_ptr<RealMicFactory> factory) {
|
||||
g_factory = std::move(factory);
|
||||
}
|
||||
|
||||
std::unique_ptr<Interface> CreateRealMic(std::string mic_device_name) {
|
||||
return g_factory->Create(std::move(mic_device_name));
|
||||
}
|
||||
|
||||
} // namespace Frontend::Mic
|
@ -1,137 +0,0 @@
|
||||
// Copyright 2019 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include "common/swap.h"
|
||||
#include "common/threadsafe_queue.h"
|
||||
|
||||
namespace Frontend::Mic {
|
||||
|
||||
constexpr char default_device_name[] = "Default";
|
||||
|
||||
enum class Signedness : u8 {
|
||||
Signed,
|
||||
Unsigned,
|
||||
};
|
||||
|
||||
using Samples = std::vector<u8>;
|
||||
|
||||
struct Parameters {
|
||||
Signedness sign;
|
||||
u8 sample_size;
|
||||
bool buffer_loop;
|
||||
u32 sample_rate;
|
||||
u32 buffer_offset;
|
||||
u32 buffer_size;
|
||||
};
|
||||
|
||||
class Interface {
|
||||
public:
|
||||
Interface() = default;
|
||||
|
||||
virtual ~Interface();
|
||||
|
||||
/// Starts the microphone. Called by Core
|
||||
virtual void StartSampling(const Parameters& params) = 0;
|
||||
|
||||
/// 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;
|
||||
|
||||
/**
|
||||
* Adjusts the Parameters. Implementations should update the parameters field in addition to
|
||||
* changing the mic to sample according to the new parameters. Called by Core
|
||||
*/
|
||||
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 Parameters& GetParameters() const {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
protected:
|
||||
Parameters parameters;
|
||||
u8 gain = 0;
|
||||
bool is_sampling = false;
|
||||
bool powered = false;
|
||||
};
|
||||
|
||||
class NullMic final : public Interface {
|
||||
public:
|
||||
void StartSampling(const Parameters& params) override;
|
||||
|
||||
void StopSampling() override;
|
||||
|
||||
void AdjustSampleRate(u32 sample_rate) override;
|
||||
|
||||
Samples Read() override;
|
||||
};
|
||||
|
||||
class StaticMic final : public Interface {
|
||||
public:
|
||||
StaticMic();
|
||||
~StaticMic() override;
|
||||
|
||||
void StartSampling(const Parameters& params) override;
|
||||
void StopSampling() override;
|
||||
void AdjustSampleRate(u32 sample_rate) override;
|
||||
|
||||
Samples Read() override;
|
||||
|
||||
private:
|
||||
u16 sample_rate = 0;
|
||||
u8 sample_size = 0;
|
||||
std::vector<u8> CACHE_8_BIT;
|
||||
std::vector<u8> CACHE_16_BIT;
|
||||
};
|
||||
|
||||
/// Factory for creating a real Mic input device;
|
||||
class RealMicFactory {
|
||||
public:
|
||||
virtual ~RealMicFactory();
|
||||
|
||||
virtual std::unique_ptr<Interface> Create(std::string mic_device_name) = 0;
|
||||
};
|
||||
|
||||
class NullFactory final : public RealMicFactory {
|
||||
public:
|
||||
~NullFactory() override;
|
||||
|
||||
std::unique_ptr<Interface> Create(std::string mic_device_name) override;
|
||||
};
|
||||
|
||||
void RegisterRealMicFactory(std::unique_ptr<RealMicFactory> factory);
|
||||
|
||||
std::unique_ptr<Interface> CreateRealMic(std::string mic_device_name);
|
||||
|
||||
} // namespace Frontend::Mic
|
Loading…
Reference in New Issue