10 changed files with 269 additions and 6 deletions
-
2CMakeLists.txt
-
6externals/CMakeLists.txt
-
7src/audio_core/CMakeLists.txt
-
13src/audio_core/audio_out.cpp
-
2src/audio_core/audio_out.h
-
190src/audio_core/cubeb_sink.cpp
-
31src/audio_core/cubeb_sink.h
-
6src/audio_core/sink_details.cpp
-
11src/audio_core/stream.cpp
-
7src/audio_core/stream.h
@ -0,0 +1,190 @@ |
|||
// Copyright 2018 yuzu Emulator Project
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include <algorithm>
|
|||
#include <cstring>
|
|||
|
|||
#include "audio_core/cubeb_sink.h"
|
|||
#include "audio_core/stream.h"
|
|||
#include "common/logging/log.h"
|
|||
|
|||
namespace AudioCore { |
|||
|
|||
class SinkStreamImpl final : public SinkStream { |
|||
public: |
|||
SinkStreamImpl(cubeb* ctx, cubeb_devid output_device) : ctx{ctx} { |
|||
cubeb_stream_params params; |
|||
params.rate = 48000; |
|||
params.channels = GetNumChannels(); |
|||
params.format = CUBEB_SAMPLE_S16NE; |
|||
params.layout = CUBEB_LAYOUT_STEREO; |
|||
|
|||
u32 minimum_latency = 0; |
|||
if (cubeb_get_min_latency(ctx, ¶ms, &minimum_latency) != CUBEB_OK) { |
|||
LOG_CRITICAL(Audio_Sink, "Error getting minimum latency"); |
|||
} |
|||
|
|||
if (cubeb_stream_init(ctx, &stream_backend, "yuzu Audio Output", nullptr, nullptr, |
|||
output_device, ¶ms, std::max(512u, minimum_latency), |
|||
&SinkStreamImpl::DataCallback, &SinkStreamImpl::StateCallback, |
|||
this) != CUBEB_OK) { |
|||
LOG_CRITICAL(Audio_Sink, "Error initializing cubeb stream"); |
|||
return; |
|||
} |
|||
|
|||
if (cubeb_stream_start(stream_backend) != CUBEB_OK) { |
|||
LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream"); |
|||
return; |
|||
} |
|||
} |
|||
|
|||
~SinkStreamImpl() { |
|||
if (!ctx) { |
|||
return; |
|||
} |
|||
|
|||
if (cubeb_stream_stop(stream_backend) != CUBEB_OK) { |
|||
LOG_CRITICAL(Audio_Sink, "Error stopping cubeb stream"); |
|||
} |
|||
|
|||
cubeb_stream_destroy(stream_backend); |
|||
} |
|||
|
|||
void EnqueueSamples(u32 num_channels, const s16* samples, size_t sample_count) override { |
|||
if (!ctx) { |
|||
return; |
|||
} |
|||
|
|||
queue.reserve(queue.size() + sample_count * GetNumChannels()); |
|||
|
|||
if (num_channels == 2) { |
|||
// Copy as-is
|
|||
std::copy(samples, samples + sample_count * GetNumChannels(), |
|||
std::back_inserter(queue)); |
|||
} else if (num_channels == 6) { |
|||
// Downsample 6 channels to 2
|
|||
const size_t sample_count_copy_size = sample_count * num_channels * 2; |
|||
queue.reserve(sample_count_copy_size); |
|||
for (size_t i = 0; i < sample_count * num_channels; i += num_channels) { |
|||
queue.push_back(samples[i]); |
|||
queue.push_back(samples[i + 1]); |
|||
} |
|||
} else { |
|||
ASSERT_MSG(false, "Unimplemented"); |
|||
} |
|||
} |
|||
|
|||
u32 GetNumChannels() const { |
|||
// Only support 2-channel stereo output for now
|
|||
return 2; |
|||
} |
|||
|
|||
private: |
|||
std::vector<std::string> device_list; |
|||
|
|||
cubeb* ctx{}; |
|||
cubeb_stream* stream_backend{}; |
|||
|
|||
std::vector<s16> queue; |
|||
|
|||
static long DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer, |
|||
void* output_buffer, long num_frames); |
|||
static void StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state); |
|||
}; |
|||
|
|||
CubebSink::CubebSink(std::string target_device_name) { |
|||
if (cubeb_init(&ctx, "yuzu", nullptr) != CUBEB_OK) { |
|||
LOG_CRITICAL(Audio_Sink, "cubeb_init failed"); |
|||
return; |
|||
} |
|||
|
|||
if (target_device_name != auto_device_name && !target_device_name.empty()) { |
|||
cubeb_device_collection collection; |
|||
if (cubeb_enumerate_devices(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 device{std::find_if(collection.device, collection_end, |
|||
[&](const cubeb_device_info& device) { |
|||
return target_device_name == device.friendly_name; |
|||
})}; |
|||
if (device != collection_end) { |
|||
output_device = device->devid; |
|||
} |
|||
cubeb_device_collection_destroy(ctx, &collection); |
|||
} |
|||
} |
|||
} |
|||
|
|||
CubebSink::~CubebSink() { |
|||
if (!ctx) { |
|||
return; |
|||
} |
|||
|
|||
for (auto& sink_stream : sink_streams) { |
|||
sink_stream.reset(); |
|||
} |
|||
|
|||
cubeb_destroy(ctx); |
|||
} |
|||
|
|||
SinkStream& CubebSink::AcquireSinkStream(u32 sample_rate, u32 num_channels) { |
|||
sink_streams.push_back(std::make_unique<SinkStreamImpl>(ctx, output_device)); |
|||
return *sink_streams.back(); |
|||
} |
|||
|
|||
long SinkStreamImpl::DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer, |
|||
void* output_buffer, long num_frames) { |
|||
SinkStreamImpl* impl = static_cast<SinkStreamImpl*>(user_data); |
|||
u8* buffer = reinterpret_cast<u8*>(output_buffer); |
|||
|
|||
if (!impl) { |
|||
return {}; |
|||
} |
|||
|
|||
const size_t frames_to_write{ |
|||
std::min(impl->queue.size() / impl->GetNumChannels(), static_cast<size_t>(num_frames))}; |
|||
|
|||
memcpy(buffer, impl->queue.data(), frames_to_write * sizeof(s16) * impl->GetNumChannels()); |
|||
impl->queue.erase(impl->queue.begin(), |
|||
impl->queue.begin() + frames_to_write * impl->GetNumChannels()); |
|||
|
|||
if (frames_to_write < num_frames) { |
|||
// Fill the rest of the frames with silence
|
|||
memset(buffer + frames_to_write * sizeof(s16) * impl->GetNumChannels(), 0, |
|||
(num_frames - frames_to_write) * sizeof(s16) * impl->GetNumChannels()); |
|||
} |
|||
|
|||
return num_frames; |
|||
} |
|||
|
|||
void SinkStreamImpl::StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state) {} |
|||
|
|||
std::vector<std::string> ListCubebSinkDevices() { |
|||
std::vector<std::string> device_list; |
|||
cubeb* ctx; |
|||
|
|||
if (cubeb_init(&ctx, "Citra Device Enumerator", nullptr) != CUBEB_OK) { |
|||
LOG_CRITICAL(Audio_Sink, "cubeb_init failed"); |
|||
return {}; |
|||
} |
|||
|
|||
cubeb_device_collection collection; |
|||
if (cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection) != CUBEB_OK) { |
|||
LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported"); |
|||
} else { |
|||
for (size_t i = 0; i < collection.count; i++) { |
|||
const cubeb_device_info& device = collection.device[i]; |
|||
if (device.friendly_name) { |
|||
device_list.emplace_back(device.friendly_name); |
|||
} |
|||
} |
|||
cubeb_device_collection_destroy(ctx, &collection); |
|||
} |
|||
|
|||
cubeb_destroy(ctx); |
|||
return device_list; |
|||
} |
|||
|
|||
} // namespace AudioCore
|
|||
@ -0,0 +1,31 @@ |
|||
// Copyright 2018 yuzu Emulator Project |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include <string> |
|||
#include <vector> |
|||
|
|||
#include <cubeb/cubeb.h> |
|||
|
|||
#include "audio_core/sink.h" |
|||
|
|||
namespace AudioCore { |
|||
|
|||
class CubebSink final : public Sink { |
|||
public: |
|||
explicit CubebSink(std::string device_id); |
|||
~CubebSink() override; |
|||
|
|||
SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels) override; |
|||
|
|||
private: |
|||
cubeb* ctx{}; |
|||
cubeb_devid output_device{}; |
|||
std::vector<SinkStreamPtr> sink_streams; |
|||
}; |
|||
|
|||
std::vector<std::string> ListCubebSinkDevices(); |
|||
|
|||
} // namespace AudioCore |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue