audio_core: Clean up cubeb backend. (#6677)

This commit is contained in:
Steveice10 2023-07-11 21:43:07 -07:00 committed by GitHub
parent a94af8ea62
commit f3ac6f054f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 71 additions and 70 deletions

View file

@ -37,39 +37,33 @@ CubebInput::CubebInput(std::string device_id)
} }
CubebInput::~CubebInput() { CubebInput::~CubebInput() {
if (!impl->ctx)
return;
if (impl->stream) { if (impl->stream) {
if (cubeb_stream_stop(impl->stream) != CUBEB_OK) { if (cubeb_stream_stop(impl->stream) != CUBEB_OK) {
LOG_ERROR(Audio, "Error stopping cubeb input stream."); LOG_ERROR(Audio, "Error stopping cubeb input stream.");
} }
cubeb_stream_destroy(impl->stream); cubeb_stream_destroy(impl->stream);
} }
cubeb_destroy(impl->ctx); if (impl->ctx) {
cubeb_destroy(impl->ctx);
}
} }
void CubebInput::StartSampling(const InputParameters& params) { void CubebInput::StartSampling(const InputParameters& 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 == Signedness::Unsigned) { if (params.sign == Signedness::Unsigned) {
LOG_ERROR(Audio, LOG_ERROR(Audio,
"Application requested unsupported unsigned pcm format. Falling back to signed"); "Application requested unsupported unsigned pcm format. Falling back to signed.");
} }
impl->sample_size_in_bytes = params.sample_size / 8;
parameters = params; parameters = params;
is_sampling = true; impl->sample_size_in_bytes = params.sample_size / 8;
cubeb_devid input_device = nullptr; cubeb_devid input_device = nullptr;
if (device_id != auto_device_name && !device_id.empty()) { if (device_id != auto_device_name && !device_id.empty()) {
cubeb_device_collection collection; cubeb_device_collection collection;
if (cubeb_enumerate_devices(impl->ctx, CUBEB_DEVICE_TYPE_INPUT, &collection) != CUBEB_OK) { if (cubeb_enumerate_devices(impl->ctx, CUBEB_DEVICE_TYPE_INPUT, &collection) == CUBEB_OK) {
LOG_WARNING(Audio, "Audio input device enumeration not supported");
} else {
const auto collection_end = collection.device + collection.count; const auto collection_end = collection.device + collection.count;
const auto device = std::find_if( const auto device = std::find_if(
collection.device, collection_end, [this](const cubeb_device_info& info) { collection.device, collection_end, [this](const cubeb_device_info& info) {
@ -79,39 +73,42 @@ void CubebInput::StartSampling(const InputParameters& params) {
input_device = device->devid; input_device = device->devid;
} }
cubeb_device_collection_destroy(impl->ctx, &collection); cubeb_device_collection_destroy(impl->ctx, &collection);
} else {
LOG_WARNING(Audio_Sink,
"Audio input device enumeration not supported, using default device.");
} }
} }
cubeb_stream_params input_params; cubeb_stream_params input_params = {
input_params.channels = 1; .format = CUBEB_SAMPLE_S16LE,
input_params.layout = CUBEB_LAYOUT_UNDEFINED; .rate = params.sample_rate,
input_params.prefs = CUBEB_STREAM_PREF_NONE; .channels = 1,
input_params.format = CUBEB_SAMPLE_S16LE; .layout = CUBEB_LAYOUT_UNDEFINED,
input_params.rate = params.sample_rate; };
u32 latency_frames = 512; // Firefox default u32 latency_frames = 512; // Firefox default
if (cubeb_get_min_latency(impl->ctx, &input_params, &latency_frames) != CUBEB_OK) { if (cubeb_get_min_latency(impl->ctx, &input_params, &latency_frames) != CUBEB_OK) {
LOG_ERROR(Audio, "Could not get minimum latency"); LOG_WARNING(Audio, "Error getting minimum input latency, falling back to default latency.");
} }
if (cubeb_stream_init(impl->ctx, &impl->stream, "Citra Microphone", input_device, &input_params, if (cubeb_stream_init(impl->ctx, &impl->stream, "Citra Microphone", input_device, &input_params,
nullptr, nullptr, latency_frames, Impl::DataCallback, Impl::StateCallback, nullptr, nullptr, latency_frames, Impl::DataCallback, Impl::StateCallback,
impl.get()) != CUBEB_OK) { impl.get()) != CUBEB_OK) {
LOG_CRITICAL(Audio, "Error creating cubeb input stream"); LOG_CRITICAL(Audio, "Error creating cubeb input stream.");
is_sampling = false;
return; return;
} }
if (cubeb_stream_start(impl->stream) != CUBEB_OK) { if (cubeb_stream_start(impl->stream) != CUBEB_OK) {
LOG_CRITICAL(Audio, "Error starting cubeb input stream"); LOG_CRITICAL(Audio, "Error starting cubeb input stream.");
is_sampling = false; cubeb_stream_destroy(impl->stream);
impl->stream = nullptr;
return; return;
} }
is_sampling = true;
} }
void CubebInput::StopSampling() { void CubebInput::StopSampling() {
// TODO(xperia64): Destroy the stream for now to avoid a leak because StartSampling
// reinitializes the stream every time
if (impl->stream) { if (impl->stream) {
cubeb_stream_stop(impl->stream); cubeb_stream_stop(impl->stream);
cubeb_stream_destroy(impl->stream); cubeb_stream_destroy(impl->stream);
@ -121,8 +118,14 @@ void CubebInput::StopSampling() {
} }
void CubebInput::AdjustSampleRate(u32 sample_rate) { void CubebInput::AdjustSampleRate(u32 sample_rate) {
// TODO This should restart the stream with the new sample rate if (!is_sampling) {
LOG_ERROR(Audio, "AdjustSampleRate unimplemented!"); return;
}
auto new_parameters = parameters;
new_parameters.sample_rate = sample_rate;
StopSampling();
StartSampling(new_parameters);
} }
Samples CubebInput::Read() { Samples CubebInput::Read() {
@ -136,7 +139,7 @@ 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); auto impl = static_cast<Impl*>(user_data);
if (!impl) { if (!impl) {
return 0; return 0;
} }
@ -177,9 +180,7 @@ std::vector<std::string> ListCubebInputDevices() {
} }
cubeb_device_collection collection; cubeb_device_collection collection;
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");
} else {
for (std::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) {
@ -187,6 +188,8 @@ std::vector<std::string> ListCubebInputDevices() {
} }
} }
cubeb_device_collection_destroy(ctx, &collection); cubeb_device_collection_destroy(ctx, &collection);
} else {
LOG_WARNING(Audio_Sink, "Audio input device enumeration not supported.");
} }
cubeb_destroy(ctx); cubeb_destroy(ctx);

View file

@ -13,8 +13,6 @@
namespace AudioCore { namespace AudioCore {
struct CubebSink::Impl { struct CubebSink::Impl {
unsigned int sample_rate = 0;
cubeb* ctx = nullptr; cubeb* ctx = nullptr;
cubeb_stream* stream = nullptr; cubeb_stream* stream = nullptr;
@ -31,28 +29,29 @@ CubebSink::CubebSink(std::string_view target_device_name) : impl(std::make_uniqu
LOG_CRITICAL(Audio_Sink, "cubeb_init failed"); LOG_CRITICAL(Audio_Sink, "cubeb_init failed");
return; return;
} }
cubeb_set_log_callback(CUBEB_LOG_NORMAL, &Impl::LogCallback);
impl->sample_rate = native_sample_rate; if (cubeb_set_log_callback(CUBEB_LOG_NORMAL, &Impl::LogCallback) != CUBEB_OK) {
LOG_CRITICAL(Audio_Sink, "cubeb_set_log_callback failed");
return;
}
cubeb_stream_params params; cubeb_stream_params params = {
params.rate = impl->sample_rate; .format = CUBEB_SAMPLE_S16LE,
params.channels = 2; .rate = native_sample_rate,
params.layout = CUBEB_LAYOUT_STEREO; .channels = 2,
params.format = CUBEB_SAMPLE_S16NE; .layout = CUBEB_LAYOUT_STEREO,
params.prefs = CUBEB_STREAM_PREF_PERSIST; };
u32 minimum_latency = 100 * impl->sample_rate / 1000; // Firefox default u32 minimum_latency = 100 * native_sample_rate / 1000; // Firefox default
if (cubeb_get_min_latency(impl->ctx, &params, &minimum_latency) != CUBEB_OK) { if (cubeb_get_min_latency(impl->ctx, &params, &minimum_latency) != CUBEB_OK) {
LOG_CRITICAL(Audio_Sink, "Error getting minimum latency"); LOG_WARNING(Audio_Sink,
"Error getting minimum output latency, falling back to default latency.");
} }
cubeb_devid output_device = nullptr; cubeb_devid output_device = nullptr;
if (target_device_name != auto_device_name && !target_device_name.empty()) { if (target_device_name != auto_device_name && !target_device_name.empty()) {
cubeb_device_collection collection; cubeb_device_collection collection;
if (cubeb_enumerate_devices(impl->ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection) != CUBEB_OK) { if (cubeb_enumerate_devices(impl->ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection) == CUBEB_OK) {
LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported");
} else {
const auto collection_end{collection.device + collection.count}; const auto collection_end{collection.device + collection.count};
const auto device{ const auto device{
std::find_if(collection.device, collection_end, [&](const cubeb_device_info& info) { std::find_if(collection.device, collection_end, [&](const cubeb_device_info& info) {
@ -63,12 +62,15 @@ CubebSink::CubebSink(std::string_view target_device_name) : impl(std::make_uniqu
output_device = device->devid; output_device = device->devid;
} }
cubeb_device_collection_destroy(impl->ctx, &collection); cubeb_device_collection_destroy(impl->ctx, &collection);
} else {
LOG_WARNING(Audio_Sink,
"Audio output device enumeration not supported, using default device.");
} }
} }
int stream_err = cubeb_stream_init(impl->ctx, &impl->stream, "CitraAudio", nullptr, nullptr, auto stream_err = cubeb_stream_init(impl->ctx, &impl->stream, "CitraAudio", nullptr, nullptr,
output_device, &params, std::max(512u, minimum_latency), output_device, &params, std::max(512u, minimum_latency),
&Impl::DataCallback, &Impl::StateCallback, impl.get()); &Impl::DataCallback, &Impl::StateCallback, impl.get());
if (stream_err != CUBEB_OK) { if (stream_err != CUBEB_OK) {
switch (stream_err) { switch (stream_err) {
case CUBEB_ERROR: case CUBEB_ERROR:
@ -92,23 +94,20 @@ CubebSink::CubebSink(std::string_view target_device_name) : impl(std::make_uniqu
} }
CubebSink::~CubebSink() { CubebSink::~CubebSink() {
if (!impl->ctx) { if (impl->stream) {
return; if (cubeb_stream_stop(impl->stream) != CUBEB_OK) {
LOG_ERROR(Audio_Sink, "Error stopping cubeb stream.");
}
cubeb_stream_destroy(impl->stream);
} }
if (cubeb_stream_stop(impl->stream) != CUBEB_OK) { if (impl->ctx) {
LOG_CRITICAL(Audio_Sink, "Error stopping cubeb stream"); cubeb_destroy(impl->ctx);
} }
cubeb_stream_destroy(impl->stream);
cubeb_destroy(impl->ctx);
} }
unsigned int CubebSink::GetNativeSampleRate() const { unsigned int CubebSink::GetNativeSampleRate() const {
if (!impl->ctx) return native_sample_rate;
return native_sample_rate;
return impl->sample_rate;
} }
void CubebSink::SetCallback(std::function<void(s16*, std::size_t)> cb) { void CubebSink::SetCallback(std::function<void(s16*, std::size_t)> cb) {
@ -121,13 +120,12 @@ long CubebSink::Impl::DataCallback(cubeb_stream* stream, void* user_data, const
auto* buffer = static_cast<s16*>(output_buffer); auto* buffer = static_cast<s16*>(output_buffer);
if (!impl || !impl->cb) { if (!impl || !impl->cb) {
LOG_DEBUG(Audio_Sink, "Emitting zeros"); LOG_DEBUG(Audio_Sink, "Missing internal data and/or audio callback, emitting zeroes.");
std::memset(output_buffer, 0, num_frames * 2 * sizeof(s16)); std::memset(output_buffer, 0, num_frames * 2 * sizeof(s16));
return num_frames; } else {
impl->cb(buffer, num_frames);
} }
impl->cb(buffer, num_frames);
return num_frames; return num_frames;
} }
@ -149,7 +147,7 @@ void CubebSink::Impl::StateCallback(cubeb_stream* stream, void* user_data, cubeb
} }
void CubebSink::Impl::LogCallback(char const* format, ...) { void CubebSink::Impl::LogCallback(char const* format, ...) {
std::array<char, 512> buffer; std::array<char, 512> buffer{};
std::va_list args; std::va_list args;
va_start(args, format); va_start(args, format);
#ifdef _MSC_VER #ifdef _MSC_VER
@ -166,15 +164,13 @@ std::vector<std::string> ListCubebSinkDevices() {
std::vector<std::string> device_list; std::vector<std::string> device_list;
cubeb* ctx; cubeb* ctx;
if (cubeb_init(&ctx, "CitraEnumerator", nullptr) != CUBEB_OK) { if (cubeb_init(&ctx, "Citra Output Device Enumerator", nullptr) != CUBEB_OK) {
LOG_CRITICAL(Audio_Sink, "cubeb_init failed"); LOG_CRITICAL(Audio_Sink, "cubeb_init failed");
return {}; return {};
} }
cubeb_device_collection collection; cubeb_device_collection collection;
if (cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection) != CUBEB_OK) { if (cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection) == CUBEB_OK) {
LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported");
} else {
for (std::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) {
@ -182,6 +178,8 @@ std::vector<std::string> ListCubebSinkDevices() {
} }
} }
cubeb_device_collection_destroy(ctx, &collection); cubeb_device_collection_destroy(ctx, &collection);
} else {
LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported.");
} }
cubeb_destroy(ctx); cubeb_destroy(ctx);