Merge pull request #855 from bunnei/cubeb
Audio output backend based on cubeb
This commit is contained in:
commit
bf9c62bc76
|
@ -7,6 +7,9 @@
|
|||
[submodule "catch"]
|
||||
path = externals/catch
|
||||
url = https://github.com/philsquared/Catch.git
|
||||
[submodule "cubeb"]
|
||||
path = externals/cubeb
|
||||
url = https://github.com/kinetiknz/cubeb.git
|
||||
[submodule "dynarmic"]
|
||||
path = externals/dynarmic
|
||||
url = https://github.com/MerryMage/dynarmic.git
|
||||
|
|
|
@ -17,6 +17,8 @@ CMAKE_DEPENDENT_OPTION(YUZU_USE_BUNDLED_QT "Download bundled Qt binaries" ON "EN
|
|||
|
||||
option(YUZU_USE_BUNDLED_UNICORN "Build/Download bundled Unicorn" ON)
|
||||
|
||||
option(ENABLE_CUBEB "Enables the cubeb audio backend" ON)
|
||||
|
||||
if(NOT EXISTS ${CMAKE_SOURCE_DIR}/.git/hooks/pre-commit)
|
||||
message(STATUS "Copying pre-commit hook")
|
||||
file(COPY hooks/pre-commit
|
||||
|
|
|
@ -54,3 +54,9 @@ endif()
|
|||
# Opus
|
||||
add_subdirectory(opus)
|
||||
target_include_directories(opus INTERFACE ./opus/include)
|
||||
|
||||
# Cubeb
|
||||
if(ENABLE_CUBEB)
|
||||
set(BUILD_TESTS OFF CACHE BOOL "")
|
||||
add_subdirectory(cubeb)
|
||||
endif()
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 12b78c0edfa40007e41dbdcd9dfe367fbb98d018
|
|
@ -2,10 +2,22 @@ add_library(audio_core STATIC
|
|||
audio_out.cpp
|
||||
audio_out.h
|
||||
buffer.h
|
||||
cubeb_sink.cpp
|
||||
cubeb_sink.h
|
||||
null_sink.h
|
||||
stream.cpp
|
||||
stream.h
|
||||
sink.h
|
||||
sink_details.cpp
|
||||
sink_details.h
|
||||
sink_stream.h
|
||||
)
|
||||
|
||||
create_target_directory_groups(audio_core)
|
||||
|
||||
target_link_libraries(audio_core PUBLIC common core)
|
||||
|
||||
if(ENABLE_CUBEB)
|
||||
target_link_libraries(audio_core PRIVATE cubeb)
|
||||
target_compile_definitions(audio_core PRIVATE -DHAVE_CUBEB=1)
|
||||
endif()
|
||||
|
|
|
@ -3,13 +3,15 @@
|
|||
// Refer to the license.txt file included.
|
||||
|
||||
#include "audio_core/audio_out.h"
|
||||
#include "audio_core/sink.h"
|
||||
#include "audio_core/sink_details.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
/// Returns the stream format from the specified number of channels
|
||||
static Stream::Format ChannelsToStreamFormat(int num_channels) {
|
||||
static Stream::Format ChannelsToStreamFormat(u32 num_channels) {
|
||||
switch (num_channels) {
|
||||
case 1:
|
||||
return Stream::Format::Mono16;
|
||||
|
@ -24,11 +26,16 @@ static Stream::Format ChannelsToStreamFormat(int num_channels) {
|
|||
return {};
|
||||
}
|
||||
|
||||
StreamPtr AudioOut::OpenStream(int sample_rate, int num_channels,
|
||||
StreamPtr AudioOut::OpenStream(u32 sample_rate, u32 num_channels,
|
||||
Stream::ReleaseCallback&& release_callback) {
|
||||
streams.push_back(std::make_shared<Stream>(sample_rate, ChannelsToStreamFormat(num_channels),
|
||||
std::move(release_callback)));
|
||||
return streams.back();
|
||||
if (!sink) {
|
||||
const SinkDetails& sink_details = GetSinkDetails("auto");
|
||||
sink = sink_details.factory("");
|
||||
}
|
||||
|
||||
return std::make_shared<Stream>(sample_rate, ChannelsToStreamFormat(num_channels),
|
||||
std::move(release_callback),
|
||||
sink->AcquireSinkStream(sample_rate, num_channels));
|
||||
}
|
||||
|
||||
std::vector<u64> AudioOut::GetTagsAndReleaseBuffers(StreamPtr stream, size_t max_count) {
|
||||
|
|
|
@ -8,20 +8,19 @@
|
|||
#include <vector>
|
||||
|
||||
#include "audio_core/buffer.h"
|
||||
#include "audio_core/sink.h"
|
||||
#include "audio_core/stream.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
using StreamPtr = std::shared_ptr<Stream>;
|
||||
|
||||
/**
|
||||
* Represents an audio playback interface, used to open and play audio streams
|
||||
*/
|
||||
class AudioOut {
|
||||
public:
|
||||
/// Opens a new audio stream
|
||||
StreamPtr OpenStream(int sample_rate, int num_channels,
|
||||
StreamPtr OpenStream(u32 sample_rate, u32 num_channels,
|
||||
Stream::ReleaseCallback&& release_callback);
|
||||
|
||||
/// Returns a vector of recently released buffers specified by tag for the specified stream
|
||||
|
@ -37,8 +36,7 @@ public:
|
|||
bool QueueBuffer(StreamPtr stream, Buffer::Tag tag, std::vector<u8>&& data);
|
||||
|
||||
private:
|
||||
/// Active audio streams on the interface
|
||||
std::vector<StreamPtr> streams;
|
||||
SinkPtr sink;
|
||||
};
|
||||
|
||||
} // namespace AudioCore
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
@ -34,4 +35,6 @@ private:
|
|||
std::vector<u8> data;
|
||||
};
|
||||
|
||||
using BufferPtr = std::shared_ptr<Buffer>;
|
||||
|
||||
} // namespace AudioCore
|
||||
|
|
|
@ -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
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "audio_core/sink.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
class NullSink final : public Sink {
|
||||
public:
|
||||
explicit NullSink(std::string){};
|
||||
~NullSink() override = default;
|
||||
|
||||
SinkStream& AcquireSinkStream(u32 /*sample_rate*/, u32 /*num_channels*/) override {
|
||||
return null_sink_stream;
|
||||
}
|
||||
|
||||
private:
|
||||
struct NullSinkStreamImpl final : SinkStream {
|
||||
void EnqueueSamples(u32 /*num_channels*/, const s16* /*samples*/,
|
||||
size_t /*sample_count*/) override {}
|
||||
} null_sink_stream;
|
||||
};
|
||||
|
||||
} // namespace AudioCore
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "audio_core/sink_stream.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
constexpr char auto_device_name[] = "auto";
|
||||
|
||||
/**
|
||||
* This class is an interface for an audio sink. An audio sink accepts samples in stereo signed
|
||||
* PCM16 format to be output. Sinks *do not* handle resampling and expect the correct sample rate.
|
||||
* They are dumb outputs.
|
||||
*/
|
||||
class Sink {
|
||||
public:
|
||||
virtual ~Sink() = default;
|
||||
virtual SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels) = 0;
|
||||
};
|
||||
|
||||
using SinkPtr = std::unique_ptr<Sink>;
|
||||
|
||||
} // namespace AudioCore
|
|
@ -0,0 +1,44 @@
|
|||
// Copyright 2018 yuzu 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/null_sink.h"
|
||||
#include "audio_core/sink_details.h"
|
||||
#ifdef HAVE_CUBEB
|
||||
#include "audio_core/cubeb_sink.h"
|
||||
#endif
|
||||
#include "common/logging/log.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
// g_sink_details is ordered in terms of desirability, with the best choice at the top.
|
||||
const std::vector<SinkDetails> g_sink_details = {
|
||||
#ifdef HAVE_CUBEB
|
||||
SinkDetails{"cubeb", &std::make_unique<CubebSink, std::string>, &ListCubebSinkDevices},
|
||||
#endif
|
||||
SinkDetails{"null", &std::make_unique<NullSink, std::string>,
|
||||
[] { return std::vector<std::string>{"null"}; }},
|
||||
};
|
||||
|
||||
const SinkDetails& GetSinkDetails(std::string sink_id) {
|
||||
auto iter =
|
||||
std::find_if(g_sink_details.begin(), g_sink_details.end(),
|
||||
[sink_id](const auto& sink_detail) { return sink_detail.id == sink_id; });
|
||||
|
||||
if (sink_id == "auto" || iter == g_sink_details.end()) {
|
||||
if (sink_id != "auto") {
|
||||
LOG_ERROR(Audio, "AudioCore::SelectSink given invalid sink_id {}", sink_id);
|
||||
}
|
||||
// Auto-select.
|
||||
// g_sink_details is ordered in terms of desirability, with the best choice at the front.
|
||||
iter = g_sink_details.begin();
|
||||
}
|
||||
|
||||
return *iter;
|
||||
}
|
||||
|
||||
} // namespace AudioCore
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
class Sink;
|
||||
|
||||
struct SinkDetails {
|
||||
SinkDetails(const char* id_, std::function<std::unique_ptr<Sink>(std::string)> factory_,
|
||||
std::function<std::vector<std::string>()> list_devices_)
|
||||
: id(id_), factory(factory_), list_devices(list_devices_) {}
|
||||
|
||||
/// Name for this sink.
|
||||
const char* id;
|
||||
/// A method to call to construct an instance of this type of sink.
|
||||
std::function<std::unique_ptr<Sink>(std::string device_id)> factory;
|
||||
/// A method to call to list available devices.
|
||||
std::function<std::vector<std::string>()> list_devices;
|
||||
};
|
||||
|
||||
extern const std::vector<SinkDetails> g_sink_details;
|
||||
|
||||
const SinkDetails& GetSinkDetails(std::string sink_id);
|
||||
|
||||
} // namespace AudioCore
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
/**
|
||||
* Accepts samples in stereo signed PCM16 format to be output. Sinks *do not* handle resampling and
|
||||
* expect the correct sample rate. They are dumb outputs.
|
||||
*/
|
||||
class SinkStream {
|
||||
public:
|
||||
virtual ~SinkStream() = default;
|
||||
|
||||
/**
|
||||
* Feed stereo samples to sink.
|
||||
* @param num_channels Number of channels used.
|
||||
* @param samples Samples in interleaved stereo PCM16 format.
|
||||
* @param sample_count Number of samples.
|
||||
*/
|
||||
virtual void EnqueueSamples(u32 num_channels, const s16* samples, size_t sample_count) = 0;
|
||||
};
|
||||
|
||||
using SinkStreamPtr = std::unique_ptr<SinkStream>;
|
||||
|
||||
} // namespace AudioCore
|
|
@ -7,30 +7,37 @@
|
|||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
|
||||
#include "audio_core/sink.h"
|
||||
#include "audio_core/sink_details.h"
|
||||
#include "audio_core/stream.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
constexpr size_t MaxAudioBufferCount{32};
|
||||
|
||||
/// Returns the sample size for the specified audio stream format
|
||||
static size_t SampleSizeFromFormat(Stream::Format format) {
|
||||
u32 Stream::GetNumChannels() const {
|
||||
switch (format) {
|
||||
case Stream::Format::Mono16:
|
||||
case Format::Mono16:
|
||||
return 1;
|
||||
case Format::Stereo16:
|
||||
return 2;
|
||||
case Stream::Format::Stereo16:
|
||||
return 4;
|
||||
case Stream::Format::Multi51Channel16:
|
||||
return 12;
|
||||
};
|
||||
|
||||
case Format::Multi51Channel16:
|
||||
return 6;
|
||||
}
|
||||
LOG_CRITICAL(Audio, "Unimplemented format={}", static_cast<u32>(format));
|
||||
UNREACHABLE();
|
||||
return {};
|
||||
}
|
||||
|
||||
Stream::Stream(int sample_rate, Format format, ReleaseCallback&& release_callback)
|
||||
: sample_rate{sample_rate}, format{format}, release_callback{std::move(release_callback)} {
|
||||
u32 Stream::GetSampleSize() const {
|
||||
return GetNumChannels() * 2;
|
||||
}
|
||||
|
||||
Stream::Stream(u32 sample_rate, Format format, ReleaseCallback&& release_callback,
|
||||
SinkStream& sink_stream)
|
||||
: sample_rate{sample_rate}, format{format}, release_callback{std::move(release_callback)},
|
||||
sink_stream{sink_stream} {
|
||||
|
||||
release_event = CoreTiming::RegisterEvent(
|
||||
"Stream::Release", [this](u64 userdata, int cycles_late) { ReleaseActiveBuffer(); });
|
||||
}
|
||||
|
@ -45,7 +52,7 @@ void Stream::Stop() {
|
|||
}
|
||||
|
||||
s64 Stream::GetBufferReleaseCycles(const Buffer& buffer) const {
|
||||
const size_t num_samples{buffer.GetData().size() / SampleSizeFromFormat(format)};
|
||||
const size_t num_samples{buffer.GetData().size() / GetSampleSize()};
|
||||
return CoreTiming::usToCycles((static_cast<u64>(num_samples) * 1000000) / sample_rate);
|
||||
}
|
||||
|
||||
|
@ -68,6 +75,10 @@ void Stream::PlayNextBuffer() {
|
|||
active_buffer = queued_buffers.front();
|
||||
queued_buffers.pop();
|
||||
|
||||
sink_stream.EnqueueSamples(GetNumChannels(),
|
||||
reinterpret_cast<const s16*>(active_buffer->GetData().data()),
|
||||
active_buffer->GetData().size() / GetSampleSize());
|
||||
|
||||
CoreTiming::ScheduleEventThreadsafe(GetBufferReleaseCycles(*active_buffer), release_event, {});
|
||||
}
|
||||
|
||||
|
|
|
@ -10,14 +10,13 @@
|
|||
#include <queue>
|
||||
|
||||
#include "audio_core/buffer.h"
|
||||
#include "audio_core/sink_stream.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/core_timing.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
using BufferPtr = std::shared_ptr<Buffer>;
|
||||
|
||||
/**
|
||||
* Represents an audio stream, which is a sequence of queued buffers, to be outputed by AudioOut
|
||||
*/
|
||||
|
@ -33,7 +32,8 @@ public:
|
|||
/// Callback function type, used to change guest state on a buffer being released
|
||||
using ReleaseCallback = std::function<void()>;
|
||||
|
||||
Stream(int sample_rate, Format format, ReleaseCallback&& release_callback);
|
||||
Stream(u32 sample_rate, Format format, ReleaseCallback&& release_callback,
|
||||
SinkStream& sink_stream);
|
||||
|
||||
/// Plays the audio stream
|
||||
void Play();
|
||||
|
@ -60,6 +60,17 @@ public:
|
|||
return queued_buffers.size();
|
||||
}
|
||||
|
||||
/// Gets the sample rate
|
||||
u32 GetSampleRate() const {
|
||||
return sample_rate;
|
||||
}
|
||||
|
||||
/// Gets the number of channels
|
||||
u32 GetNumChannels() const;
|
||||
|
||||
/// Gets the sample size in bytes
|
||||
u32 GetSampleSize() const;
|
||||
|
||||
private:
|
||||
/// Current state of the stream
|
||||
enum class State {
|
||||
|
@ -76,7 +87,7 @@ private:
|
|||
/// Gets the number of core cycles when the specified buffer will be released
|
||||
s64 GetBufferReleaseCycles(const Buffer& buffer) const;
|
||||
|
||||
int sample_rate; ///< Sample rate of the stream
|
||||
u32 sample_rate; ///< Sample rate of the stream
|
||||
Format format; ///< Format of the stream
|
||||
ReleaseCallback release_callback; ///< Buffer release callback for the stream
|
||||
State state{State::Stopped}; ///< Playback state of the stream
|
||||
|
@ -84,6 +95,9 @@ private:
|
|||
BufferPtr active_buffer; ///< Actively playing buffer in the stream
|
||||
std::queue<BufferPtr> queued_buffers; ///< Buffers queued to be played in the stream
|
||||
std::queue<BufferPtr> released_buffers; ///< Buffers recently released from the stream
|
||||
SinkStream& sink_stream; ///< Output sink for the stream
|
||||
};
|
||||
|
||||
using StreamPtr = std::shared_ptr<Stream>;
|
||||
|
||||
} // namespace AudioCore
|
||||
|
|
|
@ -177,7 +177,6 @@ System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) {
|
|||
}
|
||||
|
||||
gpu_core = std::make_unique<Tegra::GPU>();
|
||||
audio_core = std::make_unique<AudioCore::AudioOut>();
|
||||
telemetry_session = std::make_unique<Core::TelemetrySession>();
|
||||
service_manager = std::make_shared<Service::SM::ServiceManager>();
|
||||
|
||||
|
@ -229,7 +228,6 @@ void System::Shutdown() {
|
|||
service_manager.reset();
|
||||
telemetry_session.reset();
|
||||
gpu_core.reset();
|
||||
audio_core.reset();
|
||||
|
||||
// Close all CPU/threading state
|
||||
cpu_barrier->NotifyEnd();
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include "audio_core/audio_out.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/arm/exclusive_monitor.h"
|
||||
#include "core/core_cpu.h"
|
||||
|
@ -132,11 +131,6 @@ public:
|
|||
return *gpu_core;
|
||||
}
|
||||
|
||||
/// Gets the AudioCore interface
|
||||
AudioCore::AudioOut& AudioCore() {
|
||||
return *audio_core;
|
||||
}
|
||||
|
||||
/// Gets the scheduler for the CPU core that is currently running
|
||||
Kernel::Scheduler& CurrentScheduler() {
|
||||
return *CurrentCpuCore().Scheduler();
|
||||
|
@ -201,7 +195,6 @@ private:
|
|||
/// AppLoader used to load the current executing application
|
||||
std::unique_ptr<Loader::AppLoader> app_loader;
|
||||
std::unique_ptr<Tegra::GPU> gpu_core;
|
||||
std::unique_ptr<AudioCore::AudioOut> audio_core;
|
||||
std::shared_ptr<Tegra::DebugContext> debug_context;
|
||||
Kernel::SharedPtr<Kernel::Process> current_process;
|
||||
std::shared_ptr<ExclusiveMonitor> cpu_exclusive_monitor;
|
||||
|
|
|
@ -25,9 +25,8 @@ constexpr int DefaultSampleRate{48000};
|
|||
|
||||
class IAudioOut final : public ServiceFramework<IAudioOut> {
|
||||
public:
|
||||
IAudioOut(AudoutParams audio_params)
|
||||
: ServiceFramework("IAudioOut"), audio_params(audio_params),
|
||||
audio_core(Core::System::GetInstance().AudioCore()) {
|
||||
IAudioOut(AudoutParams audio_params, AudioCore::AudioOut& audio_core)
|
||||
: ServiceFramework("IAudioOut"), audio_params(audio_params), audio_core(audio_core) {
|
||||
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &IAudioOut::GetAudioOutState, "GetAudioOutState"},
|
||||
|
@ -195,7 +194,7 @@ void AudOutU::OpenAudioOutImpl(Kernel::HLERequestContext& ctx) {
|
|||
// TODO(bunnei): Support more than one IAudioOut interface. When we add this, ListAudioOutsImpl
|
||||
// will likely need to be updated as well.
|
||||
ASSERT_MSG(!audio_out_interface, "Unimplemented");
|
||||
audio_out_interface = std::make_shared<IAudioOut>(std::move(params));
|
||||
audio_out_interface = std::make_shared<IAudioOut>(std::move(params), *audio_core);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 6, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
@ -212,6 +211,7 @@ AudOutU::AudOutU() : ServiceFramework("audout:u") {
|
|||
{2, &AudOutU::ListAudioOutsImpl, "ListAudioOutsAuto"},
|
||||
{3, &AudOutU::OpenAudioOutImpl, "OpenAudioOutAuto"}};
|
||||
RegisterHandlers(functions);
|
||||
audio_core = std::make_unique<AudioCore::AudioOut>();
|
||||
}
|
||||
|
||||
} // namespace Service::Audio
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "audio_core/audio_out.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
@ -33,6 +34,7 @@ public:
|
|||
|
||||
private:
|
||||
std::shared_ptr<IAudioOut> audio_out_interface;
|
||||
std::unique_ptr<AudioCore::AudioOut> audio_core;
|
||||
|
||||
void ListAudioOutsImpl(Kernel::HLERequestContext& ctx);
|
||||
void OpenAudioOutImpl(Kernel::HLERequestContext& ctx);
|
||||
|
|
Reference in New Issue