audio_core: Add initial code for keeping track of audout state.
This commit is contained in:
		
							
								
								
									
										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 | ||||
		Reference in New Issue
	
	Block a user