audio_core: Implement OpenAL backend (#6450)

This commit is contained in:
Steveice10 2023-05-01 12:17:45 -07:00 committed by GitHub
parent ce553ab995
commit 055a58f01e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
48 changed files with 1042 additions and 576 deletions

View file

@ -20,18 +20,25 @@ add_library(audio_core STATIC
hle/source.h
lle/lle.cpp
lle/lle.h
input.h
input_details.cpp
input_details.h
interpolate.cpp
interpolate.h
null_input.h
null_sink.h
precompiled_headers.h
sink.h
sink_details.cpp
sink_details.h
static_input.cpp
static_input.h
time_stretch.cpp
time_stretch.h
$<$<BOOL:${ENABLE_SDL2}>:sdl2_sink.cpp sdl2_sink.h>
$<$<BOOL:${ENABLE_CUBEB}>:cubeb_sink.cpp cubeb_sink.h cubeb_input.cpp cubeb_input.h>
$<$<BOOL:${ENABLE_OPENAL}>:openal_input.cpp openal_input.h openal_sink.cpp openal_sink.h>
)
create_target_directory_groups(audio_core)
@ -92,6 +99,12 @@ if(ENABLE_CUBEB)
target_compile_definitions(audio_core PUBLIC HAVE_CUBEB)
endif()
if(ENABLE_OPENAL)
target_link_libraries(audio_core PRIVATE OpenAL)
target_compile_definitions(audio_core PUBLIC HAVE_OPENAL)
add_definitions(-DAL_LIBTYPE_STATIC)
endif()
if (CITRA_USE_PRECOMPILED_HEADERS)
target_precompile_headers(audio_core PRIVATE precompiled_headers.h)
endif()

View file

@ -6,11 +6,14 @@
#include <vector>
#include <cubeb/cubeb.h>
#include "audio_core/cubeb_input.h"
#include "audio_core/input.h"
#include "audio_core/sink.h"
#include "common/logging/log.h"
#include "common/threadsafe_queue.h"
namespace AudioCore {
using SampleQueue = Common::SPSCQueue<Frontend::Mic::Samples>;
using SampleQueue = Common::SPSCQueue<Samples>;
struct CubebInput::Impl {
cubeb* ctx = nullptr;
@ -48,10 +51,10 @@ CubebInput::~CubebInput() {
cubeb_destroy(impl->ctx);
}
void CubebInput::StartSampling(const Frontend::Mic::Parameters& params) {
void CubebInput::StartSampling(const InputParameters& params) {
// Cubeb apparently only supports signed 16 bit PCM (and float32 which the 3ds doesn't support)
// TODO resample the input stream
if (params.sign == Frontend::Mic::Signedness::Unsigned) {
if (params.sign == Signedness::Unsigned) {
LOG_ERROR(Audio,
"Application requested unsupported unsigned pcm format. Falling back to signed");
}
@ -62,7 +65,7 @@ void CubebInput::StartSampling(const Frontend::Mic::Parameters& params) {
is_sampling = true;
cubeb_devid input_device = nullptr;
if (device_id != Frontend::Mic::default_device_name && !device_id.empty()) {
if (device_id != auto_device_name && !device_id.empty()) {
cubeb_device_collection collection;
if (cubeb_enumerate_devices(impl->ctx, CUBEB_DEVICE_TYPE_INPUT, &collection) != CUBEB_OK) {
LOG_WARNING(Audio, "Audio input device enumeration not supported");
@ -122,9 +125,9 @@ void CubebInput::AdjustSampleRate(u32 sample_rate) {
LOG_ERROR(Audio, "AdjustSampleRate unimplemented!");
}
Frontend::Mic::Samples CubebInput::Read() {
Frontend::Mic::Samples samples{};
Frontend::Mic::Samples queue;
Samples CubebInput::Read() {
Samples samples{};
Samples queue;
while (impl->sample_queue->Pop(queue)) {
samples.insert(samples.end(), queue.begin(), queue.end());
}
@ -190,10 +193,4 @@ std::vector<std::string> ListCubebInputDevices() {
return device_list;
}
CubebFactory::~CubebFactory() = default;
std::unique_ptr<Frontend::Mic::Interface> CubebFactory::Create(std::string mic_device_name) {
return std::make_unique<CubebInput>(std::move(mic_device_name));
}
} // namespace AudioCore

View file

@ -5,23 +5,24 @@
#pragma once
#include <memory>
#include <string>
#include <vector>
#include "core/frontend/mic.h"
#include "audio_core/input.h"
namespace AudioCore {
class CubebInput final : public Frontend::Mic::Interface {
class CubebInput final : public Input {
public:
explicit CubebInput(std::string device_id);
~CubebInput() override;
void StartSampling(const Frontend::Mic::Parameters& params) override;
void StartSampling(const InputParameters& params) override;
void StopSampling() override;
void AdjustSampleRate(u32 sample_rate) override;
Frontend::Mic::Samples Read() override;
Samples Read() override;
private:
struct Impl;
@ -31,11 +32,4 @@ private:
std::vector<std::string> ListCubebInputDevices();
class CubebFactory final : public Frontend::Mic::RealMicFactory {
public:
~CubebFactory() override;
std::unique_ptr<Frontend::Mic::Interface> Create(std::string mic_device_name) override;
};
} // namespace AudioCore

View file

@ -6,6 +6,7 @@
#include <cstddef>
#include <memory>
#include <string>
#include "audio_core/sink.h"
namespace AudioCore {

View file

@ -16,9 +16,8 @@ namespace AudioCore {
DspInterface::DspInterface() = default;
DspInterface::~DspInterface() = default;
void DspInterface::SetSink(std::string_view sink_id, std::string_view audio_device) {
sink = CreateSinkFromID(Settings::values.sink_id.GetValue(),
Settings::values.audio_device_id.GetValue());
void DspInterface::SetSink(AudioCore::SinkType sink_type, std::string_view audio_device) {
sink = CreateSinkFromID(sink_type, audio_device);
sink->SetCallback(
[this](s16* buffer, std::size_t num_frames) { OutputCallback(buffer, num_frames); });
time_stretcher.SetOutputSampleRate(sink->GetNativeSampleRate());

View file

@ -20,6 +20,7 @@ class DSP_DSP;
namespace AudioCore {
class Sink;
enum class SinkType : u32;
class DspInterface {
public:
@ -93,8 +94,8 @@ public:
/// Unloads the DSP program
virtual void UnloadComponent() = 0;
/// Select the sink to use based on sink id.
void SetSink(std::string_view sink_id, std::string_view audio_device);
/// Select the sink to use based on sink type.
void SetSink(SinkType sink_type, std::string_view audio_device);
/// Get the current sink
Sink& GetSink();
/// Enable/Disable audio stretching.

85
src/audio_core/input.h Normal file
View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -14,6 +14,9 @@
#ifdef HAVE_CUBEB
#include "audio_core/cubeb_sink.h"
#endif
#ifdef HAVE_OPENAL
#include "audio_core/openal_sink.h"
#endif
#include "common/logging/log.h"
namespace AudioCore {
@ -22,8 +25,10 @@ struct SinkDetails {
using FactoryFn = std::unique_ptr<Sink> (*)(std::string_view);
using ListDevicesFn = std::vector<std::string> (*)();
/// Type of this sink.
SinkType type;
/// Name for this sink.
const char* id;
std::string_view name;
/// A method to call to construct an instance of this type of sink.
FactoryFn factory;
/// A method to call to list available devices.
@ -31,61 +36,66 @@ struct SinkDetails {
};
// sink_details is ordered in terms of desirability, with the best choice at the top.
constexpr SinkDetails sink_details[] = {
constexpr std::array sink_details = {
#ifdef HAVE_CUBEB
SinkDetails{"cubeb",
SinkDetails{SinkType::Cubeb, "Cubeb",
[](std::string_view device_id) -> std::unique_ptr<Sink> {
return std::make_unique<CubebSink>(device_id);
},
&ListCubebSinkDevices},
#endif
#ifdef HAVE_OPENAL
SinkDetails{SinkType::OpenAL, "OpenAL",
[](std::string_view device_id) -> std::unique_ptr<Sink> {
return std::make_unique<OpenALSink>(std::string(device_id));
},
&ListOpenALSinkDevices},
#endif
#ifdef HAVE_SDL2
SinkDetails{"sdl2",
SinkDetails{SinkType::SDL2, "SDL2",
[](std::string_view device_id) -> std::unique_ptr<Sink> {
return std::make_unique<SDL2Sink>(std::string(device_id));
},
&ListSDL2SinkDevices},
#endif
SinkDetails{"null",
SinkDetails{SinkType::Null, "None",
[](std::string_view device_id) -> std::unique_ptr<Sink> {
return std::make_unique<NullSink>(device_id);
},
[] { return std::vector<std::string>{"null"}; }},
[] { return std::vector<std::string>{"None"}; }},
};
const SinkDetails& GetSinkDetails(std::string_view sink_id) {
auto iter =
std::find_if(std::begin(sink_details), std::end(sink_details),
[sink_id](const auto& sink_detail) { return sink_detail.id == sink_id; });
const SinkDetails& GetSinkDetails(SinkType sink_type) {
auto iter = std::find_if(
sink_details.begin(), sink_details.end(),
[sink_type](const auto& sink_detail) { return sink_detail.type == sink_type; });
if (sink_id == "auto" || iter == std::end(sink_details)) {
if (sink_id != "auto") {
LOG_ERROR(Audio, "AudioCore::SelectSink given invalid sink_id {}", sink_id);
if (sink_type == SinkType::Auto || iter == sink_details.end()) {
if (sink_type != SinkType::Auto) {
LOG_ERROR(Audio, "AudioCore::GetSinkDetails given invalid sink_type {}", sink_type);
}
// Auto-select.
// sink_details is ordered in terms of desirability, with the best choice at the front.
iter = std::begin(sink_details);
iter = sink_details.begin();
}
return *iter;
}
} // Anonymous namespace
std::vector<const char*> GetSinkIDs() {
std::vector<const char*> sink_ids(std::size(sink_details));
std::transform(std::begin(sink_details), std::end(sink_details), std::begin(sink_ids),
[](const auto& sink) { return sink.id; });
return sink_ids;
std::string_view GetSinkName(SinkType sink_type) {
if (sink_type == SinkType::Auto) {
return "Auto";
}
return GetSinkDetails(sink_type).name;
}
std::vector<std::string> GetDeviceListForSink(std::string_view sink_id) {
return GetSinkDetails(sink_id).list_devices();
std::vector<std::string> GetDeviceListForSink(SinkType sink_type) {
return GetSinkDetails(sink_type).list_devices();
}
std::unique_ptr<Sink> CreateSinkFromID(std::string_view sink_id, std::string_view device_id) {
return GetSinkDetails(sink_id).factory(device_id);
std::unique_ptr<Sink> CreateSinkFromID(SinkType sink_type, std::string_view device_id) {
return GetSinkDetails(sink_type).factory(device_id);
}
} // namespace AudioCore

View file

@ -4,21 +4,33 @@
#pragma once
#include <memory>
#include <string>
#include <string_view>
#include <vector>
#include "common/common_types.h"
namespace AudioCore {
class Sink;
/// Retrieves the IDs for all available audio sinks.
std::vector<const char*> GetSinkIDs();
enum class SinkType : u32 {
Auto = 0,
Null = 1,
Cubeb = 2,
OpenAL = 3,
SDL2 = 4,
NumSinkTypes,
};
/// Gets the name of a sink type.
std::string_view GetSinkName(SinkType sink_type);
/// Gets the list of devices for a particular sink identified by the given ID.
std::vector<std::string> GetDeviceListForSink(std::string_view sink_id);
std::vector<std::string> GetDeviceListForSink(SinkType sink_type);
/// Creates an audio sink identified by the given device ID.
std::unique_ptr<Sink> CreateSinkFromID(std::string_view sink_id, std::string_view device_id);
std::unique_ptr<Sink> CreateSinkFromID(SinkType sink_type, std::string_view device_id);
} // namespace AudioCore

View file

@ -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

View file

@ -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