audio_core: Add initial code for keeping track of audout state.
This commit is contained in:
parent
0191a1e526
commit
ab756fd068
|
@ -3,6 +3,7 @@ include_directories(.)
|
||||||
|
|
||||||
add_subdirectory(common)
|
add_subdirectory(common)
|
||||||
add_subdirectory(core)
|
add_subdirectory(core)
|
||||||
|
add_subdirectory(audio_core)
|
||||||
add_subdirectory(video_core)
|
add_subdirectory(video_core)
|
||||||
add_subdirectory(input_common)
|
add_subdirectory(input_common)
|
||||||
add_subdirectory(tests)
|
add_subdirectory(tests)
|
||||||
|
|
11
src/audio_core/CMakeLists.txt
Normal file
11
src/audio_core/CMakeLists.txt
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
add_library(audio_core STATIC
|
||||||
|
audio_out.cpp
|
||||||
|
audio_out.h
|
||||||
|
buffer.h
|
||||||
|
stream.cpp
|
||||||
|
stream.h
|
||||||
|
)
|
||||||
|
|
||||||
|
create_target_directory_groups(audio_core)
|
||||||
|
|
||||||
|
target_link_libraries(audio_core PUBLIC common core)
|
50
src/audio_core/audio_out.cpp
Normal file
50
src/audio_core/audio_out.cpp
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
// Copyright 2018 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "audio_core/audio_out.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) {
|
||||||
|
switch (num_channels) {
|
||||||
|
case 1:
|
||||||
|
return Stream::Format::Mono16;
|
||||||
|
case 2:
|
||||||
|
return Stream::Format::Stereo16;
|
||||||
|
case 6:
|
||||||
|
return Stream::Format::Multi51Channel16;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_CRITICAL(Audio, "Unimplemented num_channels={}", num_channels);
|
||||||
|
UNREACHABLE();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamPtr AudioOut::OpenStream(int sample_rate, int 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u64> AudioOut::GetTagsAndReleaseBuffers(StreamPtr stream, size_t max_count) {
|
||||||
|
return stream->GetTagsAndReleaseBuffers(max_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioOut::StartStream(StreamPtr stream) {
|
||||||
|
stream->Play();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioOut::StopStream(StreamPtr stream) {
|
||||||
|
stream->Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AudioOut::QueueBuffer(StreamPtr stream, Buffer::Tag tag, std::vector<u8>&& data) {
|
||||||
|
return stream->QueueBuffer(std::make_shared<Buffer>(tag, std::move(data)));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace AudioCore
|
44
src/audio_core/audio_out.h
Normal file
44
src/audio_core/audio_out.h
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
// Copyright 2018 yuzu 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/buffer.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,
|
||||||
|
Stream::ReleaseCallback&& release_callback);
|
||||||
|
|
||||||
|
/// Returns a vector of recently released buffers specified by tag for the specified stream
|
||||||
|
std::vector<u64> GetTagsAndReleaseBuffers(StreamPtr stream, size_t max_count);
|
||||||
|
|
||||||
|
/// Starts an audio stream for playback
|
||||||
|
void StartStream(StreamPtr stream);
|
||||||
|
|
||||||
|
/// Stops an audio stream that is currently playing
|
||||||
|
void StopStream(StreamPtr stream);
|
||||||
|
|
||||||
|
/// Queues a buffer into the specified audio stream, returns true on success
|
||||||
|
bool QueueBuffer(StreamPtr stream, Buffer::Tag tag, std::vector<u8>&& data);
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// Active audio streams on the interface
|
||||||
|
std::vector<StreamPtr> streams;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace AudioCore
|
37
src/audio_core/buffer.h
Normal file
37
src/audio_core/buffer.h
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
// Copyright 2018 yuzu 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 {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a buffer of audio samples to be played in an audio stream
|
||||||
|
*/
|
||||||
|
class Buffer {
|
||||||
|
public:
|
||||||
|
using Tag = u64;
|
||||||
|
|
||||||
|
Buffer(Tag tag, std::vector<u8>&& data) : tag{tag}, data{std::move(data)} {}
|
||||||
|
|
||||||
|
/// Returns the raw audio data for the buffer
|
||||||
|
const std::vector<u8>& GetData() const {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the buffer tag, this is provided by the game to the audout service
|
||||||
|
Tag GetTag() const {
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Tag tag;
|
||||||
|
std::vector<u8> data;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace AudioCore
|
103
src/audio_core/stream.cpp
Normal file
103
src/audio_core/stream.cpp
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
// Copyright 2018 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
#include "core/core_timing.h"
|
||||||
|
#include "core/core_timing_util.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) {
|
||||||
|
switch (format) {
|
||||||
|
case Stream::Format::Mono16:
|
||||||
|
return 2;
|
||||||
|
case Stream::Format::Stereo16:
|
||||||
|
return 4;
|
||||||
|
case Stream::Format::Multi51Channel16:
|
||||||
|
return 12;
|
||||||
|
};
|
||||||
|
|
||||||
|
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)} {
|
||||||
|
release_event = CoreTiming::RegisterEvent(
|
||||||
|
"Stream::Release", [this](u64 userdata, int cycles_late) { ReleaseActiveBuffer(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stream::Play() {
|
||||||
|
state = State::Playing;
|
||||||
|
PlayNextBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stream::Stop() {
|
||||||
|
ASSERT_MSG(false, "Unimplemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
s64 Stream::GetBufferReleaseCycles(const Buffer& buffer) const {
|
||||||
|
const size_t num_samples{buffer.GetData().size() / SampleSizeFromFormat(format)};
|
||||||
|
return CoreTiming::usToCycles((static_cast<u64>(num_samples) * 1000000) / sample_rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stream::PlayNextBuffer() {
|
||||||
|
if (!IsPlaying()) {
|
||||||
|
// Ensure we are in playing state before playing the next buffer
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (active_buffer) {
|
||||||
|
// Do not queue a new buffer if we are already playing a buffer
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queued_buffers.empty()) {
|
||||||
|
// No queued buffers - we are effectively paused
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
active_buffer = queued_buffers.front();
|
||||||
|
queued_buffers.pop();
|
||||||
|
|
||||||
|
CoreTiming::ScheduleEventThreadsafe(GetBufferReleaseCycles(*active_buffer), release_event, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stream::ReleaseActiveBuffer() {
|
||||||
|
released_buffers.push(std::move(active_buffer));
|
||||||
|
release_callback();
|
||||||
|
PlayNextBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Stream::QueueBuffer(BufferPtr&& buffer) {
|
||||||
|
if (queued_buffers.size() < MaxAudioBufferCount) {
|
||||||
|
queued_buffers.push(std::move(buffer));
|
||||||
|
PlayNextBuffer();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Stream::ContainsBuffer(Buffer::Tag tag) const {
|
||||||
|
ASSERT_MSG(false, "Unimplemented");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Buffer::Tag> Stream::GetTagsAndReleaseBuffers(size_t max_count) {
|
||||||
|
std::vector<Buffer::Tag> tags;
|
||||||
|
for (size_t count = 0; count < max_count && !released_buffers.empty(); ++count) {
|
||||||
|
tags.push_back(released_buffers.front()->GetTag());
|
||||||
|
released_buffers.pop();
|
||||||
|
}
|
||||||
|
return tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace AudioCore
|
89
src/audio_core/stream.h
Normal file
89
src/audio_core/stream.h
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
// 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>
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
|
#include "audio_core/buffer.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
|
||||||
|
*/
|
||||||
|
class Stream {
|
||||||
|
public:
|
||||||
|
/// Audio format of the stream
|
||||||
|
enum class Format {
|
||||||
|
Mono16,
|
||||||
|
Stereo16,
|
||||||
|
Multi51Channel16,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// 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);
|
||||||
|
|
||||||
|
/// Plays the audio stream
|
||||||
|
void Play();
|
||||||
|
|
||||||
|
/// Stops the audio stream
|
||||||
|
void Stop();
|
||||||
|
|
||||||
|
/// Queues a buffer into the audio stream, returns true on success
|
||||||
|
bool QueueBuffer(BufferPtr&& buffer);
|
||||||
|
|
||||||
|
/// Returns true if the audio stream contains a buffer with the specified tag
|
||||||
|
bool ContainsBuffer(Buffer::Tag tag) const;
|
||||||
|
|
||||||
|
/// Returns a vector of recently released buffers specified by tag
|
||||||
|
std::vector<Buffer::Tag> GetTagsAndReleaseBuffers(size_t max_count);
|
||||||
|
|
||||||
|
/// Returns true if the stream is currently playing
|
||||||
|
bool IsPlaying() const {
|
||||||
|
return state == State::Playing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of queued buffers
|
||||||
|
size_t GetQueueSize() const {
|
||||||
|
return queued_buffers.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// Current state of the stream
|
||||||
|
enum class State {
|
||||||
|
Stopped,
|
||||||
|
Playing,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Plays the next queued buffer in the audio stream, starting playback if necessary
|
||||||
|
void PlayNextBuffer();
|
||||||
|
|
||||||
|
/// Releases the actively playing buffer, signalling that it has been completed
|
||||||
|
void ReleaseActiveBuffer();
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
Format format; ///< Format of the stream
|
||||||
|
ReleaseCallback release_callback; ///< Buffer release callback for the stream
|
||||||
|
State state{State::Stopped}; ///< Playback state of the stream
|
||||||
|
CoreTiming::EventType* release_event{}; ///< Core timing release event for the stream
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace AudioCore
|
|
@ -305,7 +305,7 @@ add_library(core STATIC
|
||||||
|
|
||||||
create_target_directory_groups(core)
|
create_target_directory_groups(core)
|
||||||
|
|
||||||
target_link_libraries(core PUBLIC common PRIVATE video_core)
|
target_link_libraries(core PUBLIC common PRIVATE audio_core video_core)
|
||||||
target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt lz4_static unicorn)
|
target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt lz4_static unicorn)
|
||||||
|
|
||||||
if (ARCHITECTURE_x86_64)
|
if (ARCHITECTURE_x86_64)
|
||||||
|
|
Loading…
Reference in a new issue