diff --git a/Meta/Lagom/Fuzzers/FuzzFlacLoader.cpp b/Meta/Lagom/Fuzzers/FuzzFlacLoader.cpp index 1364d9efd2..d3ca3e5aad 100644 --- a/Meta/Lagom/Fuzzers/FuzzFlacLoader.cpp +++ b/Meta/Lagom/Fuzzers/FuzzFlacLoader.cpp @@ -20,7 +20,7 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) auto samples = flac->get_more_samples(); if (samples.is_error()) return 2; - if (samples.value()->sample_count() > 0) + if (samples.value().size() > 0) break; } diff --git a/Meta/Lagom/Fuzzers/FuzzMP3Loader.cpp b/Meta/Lagom/Fuzzers/FuzzMP3Loader.cpp index b53fc37731..f9b8a92c64 100644 --- a/Meta/Lagom/Fuzzers/FuzzMP3Loader.cpp +++ b/Meta/Lagom/Fuzzers/FuzzMP3Loader.cpp @@ -20,7 +20,7 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) auto samples = mp3->get_more_samples(); if (samples.is_error()) return 2; - if (samples.value()->sample_count() > 0) + if (samples.value().size() > 0) break; } diff --git a/Meta/Lagom/Fuzzers/FuzzWAVLoader.cpp b/Meta/Lagom/Fuzzers/FuzzWAVLoader.cpp index f2a767d957..e668ff317b 100644 --- a/Meta/Lagom/Fuzzers/FuzzWAVLoader.cpp +++ b/Meta/Lagom/Fuzzers/FuzzWAVLoader.cpp @@ -19,7 +19,7 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) auto samples = wav->get_more_samples(); if (samples.is_error()) return 2; - if (samples.value()->sample_count() > 0) + if (samples.value().size() > 0) break; } diff --git a/Userland/Applets/Audio/main.cpp b/Userland/Applets/Audio/main.cpp index ad70ebcd9b..586c5ca929 100644 --- a/Userland/Applets/Audio/main.cpp +++ b/Userland/Applets/Audio/main.cpp @@ -237,7 +237,7 @@ private: ErrorOr serenity_main(Main::Arguments arguments) { - TRY(Core::System::pledge("stdio recvfd sendfd rpath wpath cpath unix")); + TRY(Core::System::pledge("stdio recvfd sendfd rpath wpath cpath unix thread")); auto app = TRY(GUI::Application::try_create(arguments)); Config::pledge_domain("AudioApplet"); diff --git a/Userland/Applications/Piano/AudioPlayerLoop.cpp b/Userland/Applications/Piano/AudioPlayerLoop.cpp index 390dd21e0d..06abd53b0d 100644 --- a/Userland/Applications/Piano/AudioPlayerLoop.cpp +++ b/Userland/Applications/Piano/AudioPlayerLoop.cpp @@ -8,18 +8,20 @@ #include "AudioPlayerLoop.h" #include "TrackManager.h" +#include +#include +#include +#include +#include +#include -// Converts Piano-internal data to an Audio::LegacyBuffer that AudioServer receives -static NonnullRefPtr music_samples_to_buffer(Array samples) +static FixedArray music_samples_to_buffer(Vector& music_samples) { - Vector frames; - frames.ensure_capacity(sample_count); - for (auto sample : samples) { - Audio::Sample frame = { sample.left / (double)NumericLimits::max(), sample.right / (double)NumericLimits::max() }; - frames.unchecked_append(frame); - } - // FIXME: Handle OOM better. - return MUST(Audio::LegacyBuffer::create_with_samples(frames)); + FixedArray samples = MUST(FixedArray::try_create(music_samples.size())); + for (size_t i = 0; i < music_samples.size(); ++i) + samples[i] = { static_cast(music_samples[i].left) / AK::NumericLimits::max(), static_cast(music_samples[i].right) / AK::NumericLimits::max() }; + + return samples; } AudioPlayerLoop::AudioPlayerLoop(TrackManager& track_manager, bool& need_to_write_wav, Audio::WavWriter& wav_writer) @@ -28,24 +30,31 @@ AudioPlayerLoop::AudioPlayerLoop(TrackManager& track_manager, bool& need_to_writ , m_wav_writer(wav_writer) { m_audio_client = Audio::ConnectionFromClient::try_create().release_value_but_fixme_should_propagate_errors(); - m_audio_client->on_finish_playing_buffer = [this](int buffer_id) { - (void)buffer_id; - enqueue_audio(); - }; auto target_sample_rate = m_audio_client->get_sample_rate(); if (target_sample_rate == 0) target_sample_rate = Music::sample_rate; - m_resampler = Audio::ResampleHelper(Music::sample_rate, target_sample_rate); + m_resampler = Audio::ResampleHelper(Music::sample_rate, target_sample_rate); + + // FIXME: I said I would never write such a hack again, but here we are. + // This code should die as soon as possible anyways, so it doesn't matter. + // Please don't use this as an example to write good audio code; it's just here as a temporary hack. + Core::EventLoop::register_timer(*this, 10, true, Core::TimerShouldFireWhenNotVisible::Yes); +} + +void AudioPlayerLoop::timer_event(Core::TimerEvent&) +{ + if (m_audio_client->remaining_samples() < buffer_size) + enqueue_audio(); } void AudioPlayerLoop::enqueue_audio() { m_track_manager.fill_buffer(m_buffer); - NonnullRefPtr audio_buffer = music_samples_to_buffer(m_buffer); // FIXME: Handle OOM better. - audio_buffer = MUST(Audio::resample_buffer(m_resampler.value(), *audio_buffer)); - m_audio_client->async_enqueue(audio_buffer); + auto audio_buffer = m_resampler->resample(m_buffer); + auto real_buffer = music_samples_to_buffer(audio_buffer); + (void)m_audio_client->async_enqueue(real_buffer); // FIXME: This should be done somewhere else. if (m_need_to_write_wav) { @@ -66,5 +75,8 @@ void AudioPlayerLoop::toggle_paused() { m_should_play_audio = !m_should_play_audio; - m_audio_client->set_paused(!m_should_play_audio); + if (m_should_play_audio) + m_audio_client->async_start_playback(); + else + m_audio_client->async_pause_playback(); } diff --git a/Userland/Applications/Piano/AudioPlayerLoop.h b/Userland/Applications/Piano/AudioPlayerLoop.h index 9e50a1dee7..208adf8cd7 100644 --- a/Userland/Applications/Piano/AudioPlayerLoop.h +++ b/Userland/Applications/Piano/AudioPlayerLoop.h @@ -11,6 +11,7 @@ #include #include #include +#include #include class TrackManager; @@ -28,9 +29,11 @@ public: private: AudioPlayerLoop(TrackManager& track_manager, bool& need_to_write_wav, Audio::WavWriter& wav_writer); + virtual void timer_event(Core::TimerEvent&) override; + TrackManager& m_track_manager; Array m_buffer; - Optional> m_resampler; + Optional> m_resampler; RefPtr m_audio_client; bool m_should_play_audio = true; diff --git a/Userland/Applications/Piano/Music.h b/Userland/Applications/Piano/Music.h index 6759142482..7cb23a5286 100644 --- a/Userland/Applications/Piano/Music.h +++ b/Userland/Applications/Piano/Music.h @@ -24,7 +24,7 @@ struct Sample { }; // HACK: needs to increase with device sample rate, but all of the sample_count stuff is static for now -constexpr int sample_count = 1 << 12; +constexpr int sample_count = 1 << 10; constexpr int buffer_size = sample_count * sizeof(Sample); diff --git a/Userland/Applications/Piano/main.cpp b/Userland/Applications/Piano/main.cpp index 6f57947223..8c18b6467f 100644 --- a/Userland/Applications/Piano/main.cpp +++ b/Userland/Applications/Piano/main.cpp @@ -38,8 +38,6 @@ ErrorOr serenity_main(Main::Arguments arguments) bool need_to_write_wav = false; auto audio_loop = AudioPlayerLoop::construct(track_manager, need_to_write_wav, wav_writer); - audio_loop->enqueue_audio(); - audio_loop->enqueue_audio(); auto app_icon = GUI::Icon::default_icon("app-piano"); auto window = GUI::Window::construct(); diff --git a/Userland/Applications/SoundPlayer/CMakeLists.txt b/Userland/Applications/SoundPlayer/CMakeLists.txt index 8bae5bf34d..e20d53fab1 100644 --- a/Userland/Applications/SoundPlayer/CMakeLists.txt +++ b/Userland/Applications/SoundPlayer/CMakeLists.txt @@ -19,4 +19,4 @@ set(SOURCES ) serenity_app(SoundPlayer ICON app-sound-player) -target_link_libraries(SoundPlayer LibAudio LibDSP LibGUI LibMain) +target_link_libraries(SoundPlayer LibAudio LibDSP LibGUI LibMain LibThreading) diff --git a/Userland/Applications/SoundPlayer/PlaybackManager.cpp b/Userland/Applications/SoundPlayer/PlaybackManager.cpp index fad0b1a7b1..85a191099e 100644 --- a/Userland/Applications/SoundPlayer/PlaybackManager.cpp +++ b/Userland/Applications/SoundPlayer/PlaybackManager.cpp @@ -10,21 +10,12 @@ PlaybackManager::PlaybackManager(NonnullRefPtr connection) : m_connection(connection) { + // FIXME: The buffer enqueuing should happen on a wholly independend second thread. m_timer = Core::Timer::construct(PlaybackManager::update_rate_ms, [&]() { if (!m_loader) return; - // Make sure that we have some buffers queued up at all times: an audio dropout is the last thing we want. - if (m_enqueued_buffers.size() < always_enqueued_buffer_count) - next_buffer(); - }); - m_connection->on_finish_playing_buffer = [this](auto finished_buffer) { - auto last_buffer_in_queue = m_enqueued_buffers.dequeue(); - // A fail here would mean that the server skipped one of our buffers, which is BAD. - if (last_buffer_in_queue != finished_buffer) - dbgln("Never heard back about buffer {}, what happened?", last_buffer_in_queue); - next_buffer(); - }; + }); m_timer->stop(); m_device_sample_rate = connection->get_sample_rate(); } @@ -36,9 +27,8 @@ void PlaybackManager::set_loader(NonnullRefPtr&& loader) if (m_loader) { m_total_length = m_loader->total_samples() / static_cast(m_loader->sample_rate()); m_device_samples_per_buffer = PlaybackManager::buffer_size_ms / 1000.0f * m_device_sample_rate; - u32 source_samples_per_buffer = PlaybackManager::buffer_size_ms / 1000.0f * m_loader->sample_rate(); - m_source_buffer_size_bytes = source_samples_per_buffer * m_loader->num_channels() * m_loader->bits_per_sample() / 8; - m_resampler = Audio::ResampleHelper(m_loader->sample_rate(), m_device_sample_rate); + m_samples_to_load_per_buffer = PlaybackManager::buffer_size_ms / 1000.0f * m_loader->sample_rate(); + m_resampler = Audio::ResampleHelper(m_loader->sample_rate(), m_device_sample_rate); m_timer->start(); } else { m_timer->stop(); @@ -48,9 +38,8 @@ void PlaybackManager::set_loader(NonnullRefPtr&& loader) void PlaybackManager::stop() { set_paused(true); - m_connection->clear_buffer(true); + m_connection->async_clear_buffer(); m_last_seek = 0; - m_current_buffer = nullptr; if (m_loader) (void)m_loader->reset(); @@ -75,11 +64,10 @@ void PlaybackManager::seek(int const position) bool paused_state = m_paused; set_paused(true); - m_connection->clear_buffer(true); - m_current_buffer = nullptr; - [[maybe_unused]] auto result = m_loader->seek(position); + m_connection->clear_client_buffer(); + m_connection->async_clear_buffer(); if (!paused_state) set_paused(false); } @@ -92,7 +80,10 @@ void PlaybackManager::pause() void PlaybackManager::set_paused(bool paused) { m_paused = paused; - m_connection->set_paused(paused); + if (m_paused) + m_connection->async_pause_playback(); + else + m_connection->async_start_playback(); } bool PlaybackManager::toggle_pause() @@ -113,27 +104,26 @@ void PlaybackManager::next_buffer() if (m_paused) return; - u32 audio_server_remaining_samples = m_connection->get_remaining_samples(); - bool all_samples_loaded = (m_loader->loaded_samples() >= m_loader->total_samples()); - bool audio_server_done = (audio_server_remaining_samples == 0); + while (m_connection->remaining_samples() < m_device_samples_per_buffer * always_enqueued_buffer_count) { + bool all_samples_loaded = (m_loader->loaded_samples() >= m_loader->total_samples()); + bool audio_server_done = (m_connection->remaining_samples() == 0); - if (all_samples_loaded && audio_server_done) { - stop(); - if (on_finished_playing) - on_finished_playing(); - return; - } + if (all_samples_loaded && audio_server_done) { + stop(); + if (on_finished_playing) + on_finished_playing(); + return; + } - if (audio_server_remaining_samples < m_device_samples_per_buffer * always_enqueued_buffer_count) { - auto maybe_buffer = m_loader->get_more_samples(m_source_buffer_size_bytes); + auto maybe_buffer = m_loader->get_more_samples(m_samples_to_load_per_buffer); if (!maybe_buffer.is_error()) { - m_current_buffer = maybe_buffer.release_value(); + m_current_buffer.swap(maybe_buffer.value()); VERIFY(m_resampler.has_value()); m_resampler->reset(); // FIXME: Handle OOM better. - m_current_buffer = MUST(Audio::resample_buffer(m_resampler.value(), *m_current_buffer)); - m_connection->enqueue(*m_current_buffer); - m_enqueued_buffers.enqueue(m_current_buffer->id()); + auto resampled = MUST(FixedArray::try_create(m_resampler->resample(move(m_current_buffer)).span())); + m_current_buffer.swap(resampled); + MUST(m_connection->async_enqueue(m_current_buffer)); } } } diff --git a/Userland/Applications/SoundPlayer/PlaybackManager.h b/Userland/Applications/SoundPlayer/PlaybackManager.h index b5c6862064..d34c35a6fc 100644 --- a/Userland/Applications/SoundPlayer/PlaybackManager.h +++ b/Userland/Applications/SoundPlayer/PlaybackManager.h @@ -7,11 +7,13 @@ #pragma once +#include #include #include #include #include #include +#include #include class PlaybackManager final { @@ -32,17 +34,16 @@ public: int last_seek() const { return m_last_seek; } bool is_paused() const { return m_paused; } float total_length() const { return m_total_length; } - RefPtr current_buffer() const { return m_current_buffer; } + FixedArray const& current_buffer() const { return m_current_buffer; } NonnullRefPtr connection() const { return m_connection; } Function on_update; - Function on_load_sample_buffer; Function on_finished_playing; private: // Number of buffers we want to always keep enqueued. - static constexpr size_t always_enqueued_buffer_count = 2; + static constexpr size_t always_enqueued_buffer_count = 5; void next_buffer(); void set_paused(bool); @@ -53,12 +54,11 @@ private: float m_total_length { 0 }; size_t m_device_sample_rate { 44100 }; size_t m_device_samples_per_buffer { 0 }; - size_t m_source_buffer_size_bytes { 0 }; + size_t m_samples_to_load_per_buffer { 0 }; RefPtr m_loader { nullptr }; NonnullRefPtr m_connection; - RefPtr m_current_buffer; - Queue m_enqueued_buffers; - Optional> m_resampler; + FixedArray m_current_buffer; + Optional> m_resampler; RefPtr m_timer; // Controls the GUI update rate. A smaller value makes the visualizations nicer. diff --git a/Userland/Applications/SoundPlayer/Player.cpp b/Userland/Applications/SoundPlayer/Player.cpp index 5f32f599f8..30dee3becb 100644 --- a/Userland/Applications/SoundPlayer/Player.cpp +++ b/Userland/Applications/SoundPlayer/Player.cpp @@ -12,7 +12,7 @@ Player::Player(Audio::ConnectionFromClient& audio_client_connection) , m_playback_manager(audio_client_connection) { m_playback_manager.on_update = [&]() { - auto samples_played = m_audio_client_connection.get_played_samples(); + auto samples_played = m_playback_manager.loader()->loaded_samples(); auto sample_rate = m_playback_manager.loader()->sample_rate(); float source_to_dest_ratio = static_cast(sample_rate) / m_playback_manager.device_sample_rate(); samples_played *= source_to_dest_ratio; diff --git a/Userland/Applications/SoundPlayer/Player.h b/Userland/Applications/SoundPlayer/Player.h index 6980f62e7e..564aba82f5 100644 --- a/Userland/Applications/SoundPlayer/Player.h +++ b/Userland/Applications/SoundPlayer/Player.h @@ -11,6 +11,7 @@ #include "Playlist.h" #include "PlaylistWidget.h" #include +#include class Player { public: @@ -72,7 +73,7 @@ public: virtual void volume_changed(double) = 0; virtual void mute_changed(bool) = 0; virtual void total_samples_changed(int) = 0; - virtual void sound_buffer_played(RefPtr, [[maybe_unused]] int sample_rate, [[maybe_unused]] int samples_played) = 0; + virtual void sound_buffer_played(FixedArray const&, [[maybe_unused]] int sample_rate, [[maybe_unused]] int samples_played) = 0; protected: void done_initializing() diff --git a/Userland/Applications/SoundPlayer/SoundPlayerWidgetAdvancedView.cpp b/Userland/Applications/SoundPlayer/SoundPlayerWidgetAdvancedView.cpp index f01d6c0a31..ab4899210a 100644 --- a/Userland/Applications/SoundPlayer/SoundPlayerWidgetAdvancedView.cpp +++ b/Userland/Applications/SoundPlayer/SoundPlayerWidgetAdvancedView.cpp @@ -216,7 +216,7 @@ void SoundPlayerWidgetAdvancedView::total_samples_changed(int total_samples) m_playback_progress_slider->set_page_step(total_samples / 10); } -void SoundPlayerWidgetAdvancedView::sound_buffer_played(RefPtr buffer, int sample_rate, int samples_played) +void SoundPlayerWidgetAdvancedView::sound_buffer_played(FixedArray const& buffer, int sample_rate, int samples_played) { m_visualization->set_buffer(buffer); m_visualization->set_samplerate(sample_rate); diff --git a/Userland/Applications/SoundPlayer/SoundPlayerWidgetAdvancedView.h b/Userland/Applications/SoundPlayer/SoundPlayerWidgetAdvancedView.h index 0701620b49..858a07c11e 100644 --- a/Userland/Applications/SoundPlayer/SoundPlayerWidgetAdvancedView.h +++ b/Userland/Applications/SoundPlayer/SoundPlayerWidgetAdvancedView.h @@ -11,6 +11,7 @@ #include "PlaybackManager.h" #include "Player.h" #include "VisualizationWidget.h" +#include #include #include #include @@ -46,7 +47,7 @@ public: virtual void volume_changed(double) override; virtual void mute_changed(bool) override; virtual void total_samples_changed(int) override; - virtual void sound_buffer_played(RefPtr, int sample_rate, int samples_played) override; + virtual void sound_buffer_played(FixedArray const&, int sample_rate, int samples_played) override; protected: void keydown_event(GUI::KeyEvent&) override; diff --git a/Userland/Applications/SoundPlayer/VisualizationWidget.h b/Userland/Applications/SoundPlayer/VisualizationWidget.h index d38f8f937b..cab2e8d3d8 100644 --- a/Userland/Applications/SoundPlayer/VisualizationWidget.h +++ b/Userland/Applications/SoundPlayer/VisualizationWidget.h @@ -6,6 +6,7 @@ #pragma once +#include #include #include #include @@ -13,24 +14,21 @@ #include class VisualizationWidget : public GUI::Frame { - C_OBJECT(VisualizationWidget) + C_OBJECT_ABSTRACT(VisualizationWidget) public: virtual void render(GUI::PaintEvent&, FixedArray const& samples) = 0; - void set_buffer(RefPtr buffer) + void set_buffer(FixedArray const& buffer) { - if (buffer.is_null()) + if (buffer.is_empty()) return; - if (buffer->id() == m_last_buffer_id) - return; - m_last_buffer_id = buffer->id(); - if (m_sample_buffer.size() != static_cast(buffer->sample_count())) - m_sample_buffer.resize(buffer->sample_count()); + if (m_sample_buffer.size() != buffer.size()) + m_sample_buffer.resize(buffer.size()); - for (size_t i = 0; i < static_cast(buffer->sample_count()); i++) - m_sample_buffer.data()[i] = (buffer->samples()[i].left + buffer->samples()[i].right) / 2.; + for (size_t i = 0; i < buffer.size(); i++) + m_sample_buffer.data()[i] = (buffer[i].left + buffer[i].right) / 2.; m_frame_count = 0; } @@ -80,7 +78,6 @@ public: protected: int m_samplerate; - int m_last_buffer_id; size_t m_frame_count; Vector m_sample_buffer; FixedArray m_render_buffer; @@ -89,7 +86,6 @@ protected: VisualizationWidget() : m_samplerate(-1) - , m_last_buffer_id(-1) , m_frame_count(0) { start_timer(REFRESH_TIME_MILLISECONDS); diff --git a/Userland/Libraries/LibAudio/Buffer.h b/Userland/Libraries/LibAudio/Buffer.h index bf53291f45..e998de46df 100644 --- a/Userland/Libraries/LibAudio/Buffer.h +++ b/Userland/Libraries/LibAudio/Buffer.h @@ -22,9 +22,18 @@ #include #include #include +#include #include namespace Audio { + +static constexpr size_t AUDIO_BUFFERS_COUNT = 128; +// The audio buffer size is specifically chosen to be about 1/1000th of a second (1ms). +// This has the biggest impact on latency and performance. +// The currently chosen value was not put here with much thought and a better choice is surely possible. +static constexpr size_t AUDIO_BUFFER_SIZE = 50; +using AudioQueue = Core::SharedSingleProducerCircularQueue, AUDIO_BUFFERS_COUNT>; + using namespace AK::Exponentials; // A buffer of audio samples. diff --git a/Userland/Libraries/LibAudio/CMakeLists.txt b/Userland/Libraries/LibAudio/CMakeLists.txt index 1f6a863b1f..3a8a7f4f98 100644 --- a/Userland/Libraries/LibAudio/CMakeLists.txt +++ b/Userland/Libraries/LibAudio/CMakeLists.txt @@ -8,6 +8,7 @@ set(SOURCES FlacLoader.cpp WavWriter.cpp MP3Loader.cpp + UserSampleQueue.cpp ) set(GENERATED_SOURCES @@ -16,4 +17,4 @@ set(GENERATED_SOURCES ) serenity_lib(LibAudio audio) -target_link_libraries(LibAudio LibCore LibIPC) +target_link_libraries(LibAudio LibCore LibIPC LibThreading) diff --git a/Userland/Libraries/LibAudio/ConnectionFromClient.cpp b/Userland/Libraries/LibAudio/ConnectionFromClient.cpp index 77d747f862..8777dc4b4b 100644 --- a/Userland/Libraries/LibAudio/ConnectionFromClient.cpp +++ b/Userland/Libraries/LibAudio/ConnectionFromClient.cpp @@ -1,48 +1,130 @@ /* * Copyright (c) 2018-2020, Andreas Kling + * Copyright (c) 2022, kleines Filmröllchen * * SPDX-License-Identifier: BSD-2-Clause */ -#include +#include +#include +#include +#include +#include #include +#include +#include +#include #include namespace Audio { -// FIXME: We don't know what is a good value for this. -// Real-time audio may be improved with a lower value. -static timespec g_enqueue_wait_time { 0, 10'000'000 }; - ConnectionFromClient::ConnectionFromClient(NonnullOwnPtr socket) : IPC::ConnectionToServer(*this, move(socket)) + , m_buffer(make(MUST(AudioQueue::try_create()))) + , m_user_queue(make()) + , m_background_audio_enqueuer(Threading::Thread::construct([this]() { + // All the background thread does is run an event loop. + Core::EventLoop enqueuer_loop; + m_enqueuer_loop = &enqueuer_loop; + enqueuer_loop.exec(); + m_enqueuer_loop_destruction.lock(); + m_enqueuer_loop = nullptr; + m_enqueuer_loop_destruction.unlock(); + return (intptr_t) nullptr; + })) { + m_background_audio_enqueuer->start(); + set_buffer(*m_buffer); } -void ConnectionFromClient::enqueue(LegacyBuffer const& buffer) +ConnectionFromClient::~ConnectionFromClient() { - for (;;) { - auto success = enqueue_buffer(buffer.anonymous_buffer(), buffer.id(), buffer.sample_count()); - if (success) - break; - nanosleep(&g_enqueue_wait_time, nullptr); + die(); +} + +void ConnectionFromClient::die() +{ + // We're sometimes getting here after the other thread has already exited and its event loop does no longer exist. + m_enqueuer_loop_destruction.lock(); + if (m_enqueuer_loop != nullptr) { + m_enqueuer_loop->wake(); + m_enqueuer_loop->quit(0); } + m_enqueuer_loop_destruction.unlock(); + (void)m_background_audio_enqueuer->join(); } -void ConnectionFromClient::async_enqueue(LegacyBuffer const& buffer) +ErrorOr ConnectionFromClient::async_enqueue(FixedArray&& samples) { - async_enqueue_buffer(buffer.anonymous_buffer(), buffer.id(), buffer.sample_count()); + update_good_sleep_time(); + m_user_queue->append(move(samples)); + // Wake the background thread to make sure it starts enqueuing audio. + if (!m_audio_enqueuer_active.load()) + m_enqueuer_loop->post_event(*this, make(0), Core::EventLoop::ShouldWake::Yes); + async_start_playback(); + + return {}; } -bool ConnectionFromClient::try_enqueue(LegacyBuffer const& buffer) +void ConnectionFromClient::clear_client_buffer() { - return enqueue_buffer(buffer.anonymous_buffer(), buffer.id(), buffer.sample_count()); + m_user_queue->clear(); } -void ConnectionFromClient::finished_playing_buffer(i32 buffer_id) +void ConnectionFromClient::update_good_sleep_time() { - if (on_finish_playing_buffer) - on_finish_playing_buffer(buffer_id); + auto sample_rate = static_cast(get_sample_rate()); + auto buffer_play_time_ns = 1'000'000'000.0 / (sample_rate / static_cast(AUDIO_BUFFER_SIZE)); + // A factor of 1 should be good for now. + m_good_sleep_time = Time::from_nanoseconds(static_cast(buffer_play_time_ns)).to_timespec(); +} + +// Non-realtime audio writing loop +void ConnectionFromClient::custom_event(Core::CustomEvent&) +{ + Array next_chunk; + while (true) { + m_audio_enqueuer_active.store(true); + + if (m_user_queue->is_empty()) { + dbgln("Reached end of provided audio data, going to sleep"); + break; + } + + auto available_samples = min(AUDIO_BUFFER_SIZE, m_user_queue->size()); + for (size_t i = 0; i < available_samples; ++i) + next_chunk[i] = (*m_user_queue)[i]; + + m_user_queue->discard_samples(available_samples); + + // FIXME: Could we recieve interrupts in a good non-IPC way instead? + auto result = m_buffer->try_blocking_enqueue(next_chunk, [this]() { + nanosleep(&m_good_sleep_time, nullptr); + }); + if (result.is_error()) + dbgln("Error while writing samples to shared buffer: {}", result.error()); + } + m_audio_enqueuer_active.store(false); +} + +ErrorOr ConnectionFromClient::realtime_enqueue(Array samples) +{ + return m_buffer->try_enqueue(samples); +} + +unsigned ConnectionFromClient::total_played_samples() const +{ + return m_buffer->weak_tail() * AUDIO_BUFFER_SIZE; +} + +unsigned ConnectionFromClient::remaining_samples() +{ + return static_cast(m_user_queue->remaining_samples()); +} + +size_t ConnectionFromClient::remaining_buffers() const +{ + return m_buffer->size() - m_buffer->weak_remaining_capacity(); } void ConnectionFromClient::main_mix_muted_state_changed(bool muted) diff --git a/Userland/Libraries/LibAudio/ConnectionFromClient.h b/Userland/Libraries/LibAudio/ConnectionFromClient.h index 61ad818c42..8d204c8def 100644 --- a/Userland/Libraries/LibAudio/ConnectionFromClient.h +++ b/Userland/Libraries/LibAudio/ConnectionFromClient.h @@ -1,29 +1,61 @@ /* * Copyright (c) 2018-2020, Andreas Kling + * Copyright (c) 2022, kleines Filmröllchen * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once +#include +#include +#include +#include #include #include +#include +#include +#include +#include #include +#include +#include namespace Audio { -class LegacyBuffer; - class ConnectionFromClient final : public IPC::ConnectionToServer , public AudioClientEndpoint { IPC_CLIENT_CONNECTION(ConnectionFromClient, "/tmp/portal/audio") public: - void enqueue(LegacyBuffer const&); - bool try_enqueue(LegacyBuffer const&); - void async_enqueue(LegacyBuffer const&); + virtual ~ConnectionFromClient() override; + + // Both of these APIs are for convenience and when you don't care about real-time behavior. + // They will not work properly in conjunction with realtime_enqueue. + // If you don't refill the buffer in time with this API, the last shared buffer write is zero-padded to play all of the samples. + template Samples> + ErrorOr async_enqueue(Samples&& samples) + { + return async_enqueue(TRY(FixedArray::try_create(samples.span()))); + } + + ErrorOr async_enqueue(FixedArray&& samples); + + void clear_client_buffer(); + + // Returns immediately with the appropriate status if the buffer is full; use in conjunction with remaining_buffers to get low latency. + ErrorOr realtime_enqueue(Array samples); + + // This information can be deducted from the shared audio buffer. + unsigned total_played_samples() const; + // How many samples remain in m_enqueued_samples. + unsigned remaining_samples(); + // How many buffers (i.e. short sample arrays) the server hasn't played yet. + // Non-realtime code needn't worry about this. + size_t remaining_buffers() const; + + virtual void die() override; - Function on_finish_playing_buffer; Function on_main_mix_muted_state_change; Function on_main_mix_volume_change; Function on_client_volume_change; @@ -31,10 +63,31 @@ public: private: ConnectionFromClient(NonnullOwnPtr); - virtual void finished_playing_buffer(i32) override; virtual void main_mix_muted_state_changed(bool) override; virtual void main_mix_volume_changed(double) override; virtual void client_volume_changed(double) override; + + // We use this to perform the audio enqueuing on the background thread's event loop + virtual void custom_event(Core::CustomEvent&) override; + + // FIXME: This should be called every time the sample rate changes, but we just cautiously call it on every non-realtime enqueue. + void update_good_sleep_time(); + + // Shared audio buffer: both server and client constantly read and write to/from this. + // This needn't be mutex protected: it's internally multi-threading aware. + OwnPtr m_buffer; + + // The queue of non-realtime audio provided by the user. + NonnullOwnPtr m_user_queue; + + NonnullRefPtr m_background_audio_enqueuer; + Core::EventLoop* m_enqueuer_loop; + Threading::Mutex m_enqueuer_loop_destruction; + Atomic m_audio_enqueuer_active { false }; + + // A good amount of time to sleep when the queue is full. + // (Only used for non-realtime enqueues) + timespec m_good_sleep_time {}; }; } diff --git a/Userland/Libraries/LibAudio/FlacLoader.cpp b/Userland/Libraries/LibAudio/FlacLoader.cpp index 3a504151ab..be583f350c 100644 --- a/Userland/Libraries/LibAudio/FlacLoader.cpp +++ b/Userland/Libraries/LibAudio/FlacLoader.cpp @@ -242,7 +242,7 @@ LoaderSamples FlacLoaderPlugin::get_more_samples(size_t max_bytes_to_read_from_i { ssize_t remaining_samples = static_cast(m_total_samples - m_loaded_samples); if (remaining_samples <= 0) - return LegacyBuffer::create_empty(); + return FixedArray {}; // FIXME: samples_to_read is calculated wrong, because when seeking not all samples are loaded. size_t samples_to_read = min(max_bytes_to_read_from_input, remaining_samples); @@ -267,10 +267,8 @@ LoaderSamples FlacLoaderPlugin::get_more_samples(size_t max_bytes_to_read_from_i } m_loaded_samples += sample_index; - auto maybe_buffer = LegacyBuffer::create_with_samples(move(samples)); - if (maybe_buffer.is_error()) - return LoaderError { LoaderError::Category::Internal, m_loaded_samples, "Couldn't allocate sample buffer" }; - return maybe_buffer.release_value(); + + return samples; } MaybeLoaderError FlacLoaderPlugin::next_frame(Span target_vector) diff --git a/Userland/Libraries/LibAudio/Loader.h b/Userland/Libraries/LibAudio/Loader.h index 4d5b68cdd0..302ea13260 100644 --- a/Userland/Libraries/LibAudio/Loader.h +++ b/Userland/Libraries/LibAudio/Loader.h @@ -22,7 +22,7 @@ namespace Audio { static constexpr StringView no_plugin_error = "No loader plugin available"; -using LoaderSamples = Result, LoaderError>; +using LoaderSamples = Result, LoaderError>; using MaybeLoaderError = Result; class LoaderPlugin { @@ -60,7 +60,7 @@ public: static Result, LoaderError> create(StringView path) { return adopt_ref(*new Loader(TRY(try_create(path)))); } static Result, LoaderError> create(Bytes& buffer) { return adopt_ref(*new Loader(TRY(try_create(buffer)))); } - LoaderSamples get_more_samples(size_t max_bytes_to_read_from_input = 128 * KiB) const { return m_plugin->get_more_samples(max_bytes_to_read_from_input); } + LoaderSamples get_more_samples(size_t max_samples_to_read_from_input = 128 * KiB) const { return m_plugin->get_more_samples(max_samples_to_read_from_input); } MaybeLoaderError reset() const { return m_plugin->reset(); } MaybeLoaderError seek(int const position) const { return m_plugin->seek(position); } diff --git a/Userland/Libraries/LibAudio/MP3Loader.cpp b/Userland/Libraries/LibAudio/MP3Loader.cpp index ca0199f805..b8fc92c949 100644 --- a/Userland/Libraries/LibAudio/MP3Loader.cpp +++ b/Userland/Libraries/LibAudio/MP3Loader.cpp @@ -7,6 +7,7 @@ #include "MP3Loader.h" #include "MP3HuffmanTables.h" #include "MP3Tables.h" +#include #include #include @@ -115,18 +116,17 @@ MaybeLoaderError MP3LoaderPlugin::seek(int const position) return {}; } -LoaderSamples MP3LoaderPlugin::get_more_samples(size_t max_bytes_to_read_from_input) +LoaderSamples MP3LoaderPlugin::get_more_samples(size_t max_samples_to_read_from_input) { - Vector samples; + FixedArray samples = LOADER_TRY(FixedArray::try_create(max_samples_to_read_from_input)); - size_t samples_to_read = max_bytes_to_read_from_input; - samples.resize(samples_to_read); + size_t samples_to_read = max_samples_to_read_from_input; while (samples_to_read > 0) { if (!m_current_frame.has_value()) { auto maybe_frame = read_next_frame(); if (maybe_frame.is_error()) { if (m_input_stream->unreliable_eof()) { - return LegacyBuffer::create_empty(); + return FixedArray {}; } return maybe_frame.release_error(); } @@ -156,10 +156,7 @@ LoaderSamples MP3LoaderPlugin::get_more_samples(size_t max_bytes_to_read_from_in } m_loaded_samples += samples.size(); - auto maybe_buffer = LegacyBuffer::create_with_samples(move(samples)); - if (maybe_buffer.is_error()) - return LoaderError { LoaderError::Category::Internal, m_loaded_samples, "Couldn't allocate sample buffer" }; - return maybe_buffer.release_value(); + return samples; } MaybeLoaderError MP3LoaderPlugin::build_seek_table() diff --git a/Userland/Libraries/LibAudio/UserSampleQueue.cpp b/Userland/Libraries/LibAudio/UserSampleQueue.cpp new file mode 100644 index 0000000000..f58601849a --- /dev/null +++ b/Userland/Libraries/LibAudio/UserSampleQueue.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2022, kleines Filmröllchen + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "UserSampleQueue.h" + +namespace Audio { + +void UserSampleQueue::append(FixedArray&& samples) +{ + Threading::MutexLocker lock(m_sample_mutex); + if (m_samples_to_discard != 0) + m_backing_samples = m_backing_samples.release_slice(m_samples_to_discard); + m_backing_samples.append(move(samples)); + fix_spans(); +} + +void UserSampleQueue::clear() +{ + discard_samples(size()); +} + +void UserSampleQueue::fix_spans() +{ + Threading::MutexLocker lock(m_sample_mutex); + m_enqueued_samples = m_backing_samples.spans(); + m_samples_to_discard = 0; +} + +Sample UserSampleQueue::operator[](size_t index) +{ + Threading::MutexLocker lock(m_sample_mutex); + return m_enqueued_samples[index]; +} + +void UserSampleQueue::discard_samples(size_t count) +{ + Threading::MutexLocker lock(m_sample_mutex); + m_samples_to_discard += count; + m_enqueued_samples = m_enqueued_samples.slice(count); +} + +size_t UserSampleQueue::size() +{ + Threading::MutexLocker lock(m_sample_mutex); + return m_enqueued_samples.size(); +} + +size_t UserSampleQueue::remaining_samples() +{ + Threading::MutexLocker lock(m_sample_mutex); + return m_backing_samples.size() - m_samples_to_discard; +} + +bool UserSampleQueue::is_empty() +{ + Threading::MutexLocker lock(m_sample_mutex); + return m_enqueued_samples.is_empty(); +} + +} diff --git a/Userland/Libraries/LibAudio/UserSampleQueue.h b/Userland/Libraries/LibAudio/UserSampleQueue.h new file mode 100644 index 0000000000..34057b48a9 --- /dev/null +++ b/Userland/Libraries/LibAudio/UserSampleQueue.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2022, kleines Filmröllchen + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace Audio { + +// A sample queue providing synchronized access to efficiently-stored segmented user-provided audio data. +class UserSampleQueue { + AK_MAKE_NONCOPYABLE(UserSampleQueue); + AK_MAKE_NONMOVABLE(UserSampleQueue); + +public: + UserSampleQueue() = default; + + void append(FixedArray&& samples); + void clear(); + // Slice off some amount of samples from the beginning. + void discard_samples(size_t count); + Sample operator[](size_t index); + + // The number of samples in the span. + size_t size(); + bool is_empty(); + size_t remaining_samples(); + +private: + // Re-initialize the spans after a vector resize. + void fix_spans(); + + Threading::Mutex m_sample_mutex; + // Sample data view to keep track of what to play next. + DisjointSpans m_enqueued_samples; + // The number of samples that were played from the backing store since last discarding its start. + size_t m_samples_to_discard { 0 }; + // The backing store for the enqueued sample view. + DisjointChunks> m_backing_samples {}; +}; + +} diff --git a/Userland/Libraries/LibAudio/WavLoader.cpp b/Userland/Libraries/LibAudio/WavLoader.cpp index d5fa8ee57e..a675e8dc71 100644 --- a/Userland/Libraries/LibAudio/WavLoader.cpp +++ b/Userland/Libraries/LibAudio/WavLoader.cpp @@ -8,6 +8,7 @@ #include "WavLoader.h" #include "Buffer.h" #include +#include #include #include #include @@ -53,7 +54,7 @@ LoaderSamples WavLoaderPlugin::get_more_samples(size_t max_bytes_to_read_from_in int remaining_samples = m_total_samples - m_loaded_samples; if (remaining_samples <= 0) - return LegacyBuffer::create_empty(); + return FixedArray {}; // One "sample" contains data from all channels. // In the Wave spec, this is also called a block. @@ -88,7 +89,7 @@ LoaderSamples WavLoaderPlugin::get_more_samples(size_t max_bytes_to_read_from_in // m_loaded_samples should contain the amount of actually loaded samples m_loaded_samples += samples_to_read; - return buffer.release_value(); + return LOADER_TRY(buffer.value()->to_sample_array()); } MaybeLoaderError WavLoaderPlugin::seek(int const sample_index) diff --git a/Userland/Libraries/LibAudio/WavLoader.h b/Userland/Libraries/LibAudio/WavLoader.h index 6faf3ccbbc..6643630b98 100644 --- a/Userland/Libraries/LibAudio/WavLoader.h +++ b/Userland/Libraries/LibAudio/WavLoader.h @@ -21,7 +21,6 @@ #include namespace Audio { -class LegacyBuffer; // defines for handling the WAV header data #define WAVE_FORMAT_PCM 0x0001 // PCM diff --git a/Userland/Services/AudioServer/AudioClient.ipc b/Userland/Services/AudioServer/AudioClient.ipc index 0a6d139467..df97c95709 100644 --- a/Userland/Services/AudioServer/AudioClient.ipc +++ b/Userland/Services/AudioServer/AudioClient.ipc @@ -2,7 +2,6 @@ endpoint AudioClient { - finished_playing_buffer(i32 buffer_id) =| main_mix_muted_state_changed(bool muted) =| main_mix_volume_changed(double volume) =| client_volume_changed(double volume) =| diff --git a/Userland/Services/AudioServer/AudioServer.ipc b/Userland/Services/AudioServer/AudioServer.ipc index 0c7d24ddfc..890642d23e 100644 --- a/Userland/Services/AudioServer/AudioServer.ipc +++ b/Userland/Services/AudioServer/AudioServer.ipc @@ -1,4 +1,5 @@ #include +#include endpoint AudioServer { @@ -17,12 +18,8 @@ endpoint AudioServer get_sample_rate() => (u32 sample_rate) // Buffer playback - enqueue_buffer(Core::AnonymousBuffer buffer, i32 buffer_id, int sample_count) => (bool success) - set_paused(bool paused) => () - clear_buffer(bool paused) => () - - //Buffer information - get_remaining_samples() => (int remaining_samples) - get_played_samples() => (int played_samples) - get_playing_buffer() => (i32 buffer_id) + set_buffer(Audio::AudioQueue buffer) => () + clear_buffer() =| + start_playback() =| + pause_playback() =| } diff --git a/Userland/Services/AudioServer/ConnectionFromClient.cpp b/Userland/Services/AudioServer/ConnectionFromClient.cpp index 0f4b457561..4c3d6862c4 100644 --- a/Userland/Services/AudioServer/ConnectionFromClient.cpp +++ b/Userland/Services/AudioServer/ConnectionFromClient.cpp @@ -34,9 +34,17 @@ void ConnectionFromClient::die() s_connections.remove(client_id()); } -void ConnectionFromClient::did_finish_playing_buffer(Badge, int buffer_id) +void ConnectionFromClient::set_buffer(Audio::AudioQueue const& buffer) { - async_finished_playing_buffer(buffer_id); + if (!buffer.is_valid()) { + did_misbehave("Received an invalid buffer"); + return; + } + if (!m_queue) + m_queue = m_mixer.create_queue(*this); + + // This is ugly but we know nobody uses the buffer afterwards anyways. + m_queue->set_buffer(make(move(const_cast(buffer)))); } void ConnectionFromClient::did_change_main_mix_muted_state(Badge, bool muted) @@ -85,55 +93,22 @@ void ConnectionFromClient::set_self_volume(double volume) m_queue->set_volume(volume); } -Messages::AudioServer::EnqueueBufferResponse ConnectionFromClient::enqueue_buffer(Core::AnonymousBuffer const& buffer, i32 buffer_id, int sample_count) -{ - if (!m_queue) - m_queue = m_mixer.create_queue(*this); - - if (m_queue->is_full()) - return false; - - // There's not a big allocation to worry about here. - m_queue->enqueue(MUST(Audio::LegacyBuffer::create_with_anonymous_buffer(buffer, buffer_id, sample_count))); - return true; -} - -Messages::AudioServer::GetRemainingSamplesResponse ConnectionFromClient::get_remaining_samples() -{ - int remaining = 0; - if (m_queue) - remaining = m_queue->get_remaining_samples(); - - return remaining; -} - -Messages::AudioServer::GetPlayedSamplesResponse ConnectionFromClient::get_played_samples() -{ - int played = 0; - if (m_queue) - played = m_queue->get_played_samples(); - - return played; -} - -void ConnectionFromClient::set_paused(bool paused) +void ConnectionFromClient::start_playback() { if (m_queue) - m_queue->set_paused(paused); + m_queue->set_paused(false); } -void ConnectionFromClient::clear_buffer(bool paused) +void ConnectionFromClient::pause_playback() { if (m_queue) - m_queue->clear(paused); + m_queue->set_paused(true); } -Messages::AudioServer::GetPlayingBufferResponse ConnectionFromClient::get_playing_buffer() +void ConnectionFromClient::clear_buffer() { - int id = -1; if (m_queue) - id = m_queue->get_playing_buffer(); - return id; + m_queue->clear(); } Messages::AudioServer::IsMainMixMutedResponse ConnectionFromClient::is_main_mix_muted() diff --git a/Userland/Services/AudioServer/ConnectionFromClient.h b/Userland/Services/AudioServer/ConnectionFromClient.h index e744e75c3d..2962ecbf37 100644 --- a/Userland/Services/AudioServer/ConnectionFromClient.h +++ b/Userland/Services/AudioServer/ConnectionFromClient.h @@ -9,12 +9,10 @@ #include #include #include +#include +#include #include -namespace Audio { -class LegacyBuffer; -} - namespace AudioServer { class ClientAudioStream; @@ -25,7 +23,6 @@ class ConnectionFromClient final : public IPC::ConnectionFromClient, int buffer_id); void did_change_client_volume(Badge, double volume); void did_change_main_mix_muted_state(Badge, bool muted); void did_change_main_mix_volume(Badge, double volume); @@ -41,12 +38,10 @@ private: virtual void set_main_mix_volume(double) override; virtual Messages::AudioServer::GetSelfVolumeResponse get_self_volume() override; virtual void set_self_volume(double) override; - virtual Messages::AudioServer::EnqueueBufferResponse enqueue_buffer(Core::AnonymousBuffer const&, i32, int) override; - virtual Messages::AudioServer::GetRemainingSamplesResponse get_remaining_samples() override; - virtual Messages::AudioServer::GetPlayedSamplesResponse get_played_samples() override; - virtual void set_paused(bool) override; - virtual void clear_buffer(bool) override; - virtual Messages::AudioServer::GetPlayingBufferResponse get_playing_buffer() override; + virtual void set_buffer(Audio::AudioQueue const&) override; + virtual void clear_buffer() override; + virtual void start_playback() override; + virtual void pause_playback() override; virtual Messages::AudioServer::IsMainMixMutedResponse is_main_mix_muted() override; virtual void set_main_mix_muted(bool) override; virtual Messages::AudioServer::IsSelfMutedResponse is_self_muted() override; diff --git a/Userland/Services/AudioServer/Mixer.cpp b/Userland/Services/AudioServer/Mixer.cpp index cd86c4ee0e..fc49e36204 100644 --- a/Userland/Services/AudioServer/Mixer.cpp +++ b/Userland/Services/AudioServer/Mixer.cpp @@ -6,8 +6,8 @@ */ #include "Mixer.h" -#include "AK/Format.h" #include +#include #include #include #include @@ -200,9 +200,4 @@ ClientAudioStream::ClientAudioStream(ConnectionFromClient& client) { } -void ClientAudioStream::enqueue(NonnullRefPtr&& buffer) -{ - m_remaining_samples += buffer->sample_count(); - m_queue.enqueue(move(buffer)); -} } diff --git a/Userland/Services/AudioServer/Mixer.h b/Userland/Services/AudioServer/Mixer.h index ca24a77050..77b93b09e1 100644 --- a/Userland/Services/AudioServer/Mixer.h +++ b/Userland/Services/AudioServer/Mixer.h @@ -1,6 +1,6 @@ /* * Copyright (c) 2018-2020, Andreas Kling - * Copyright (c) 2021, kleines Filmröllchen + * Copyright (c) 2021-2022, kleines Filmröllchen * * SPDX-License-Identifier: BSD-2-Clause */ @@ -37,58 +37,43 @@ public: explicit ClientAudioStream(ConnectionFromClient&); ~ClientAudioStream() = default; - bool is_full() const { return m_queue.size() >= 3; } - void enqueue(NonnullRefPtr&&); - bool get_next_sample(Audio::Sample& sample) { if (m_paused) return false; - while (!m_current && !m_queue.is_empty()) - m_current = m_queue.dequeue(); + if (m_in_chunk_location >= m_current_audio_chunk.size()) { + // FIXME: We should send a did_misbehave to the client if the queue is empty, + // but the lifetimes involved mean that we segfault if we try to do that. + auto result = m_buffer->try_dequeue(); + if (result.is_error()) { + if (result.error() == Audio::AudioQueue::QueueStatus::Empty) + dbgln("Audio client can't keep up!"); - if (!m_current) - return false; - - sample = m_current->samples()[m_position++]; - if (m_remaining_samples > 0) - --m_remaining_samples; - ++m_played_samples; - - if (m_position >= m_current->sample_count()) { - m_client->did_finish_playing_buffer({}, m_current->id()); - m_current = nullptr; - m_position = 0; + return false; + } + m_current_audio_chunk = result.release_value(); + m_in_chunk_location = 0; } + + sample = m_current_audio_chunk[m_in_chunk_location++]; + return true; } ConnectionFromClient* client() { return m_client.ptr(); } - void clear(bool paused = false) + void set_buffer(OwnPtr buffer) { m_buffer = move(buffer); } + + void clear() { - m_queue.clear(); - m_position = 0; - m_remaining_samples = 0; - m_played_samples = 0; - m_current = nullptr; - m_paused = paused; + ErrorOr, Audio::AudioQueue::QueueStatus> result = Audio::AudioQueue::QueueStatus::Invalid; + do { + result = m_buffer->try_dequeue(); + } while (result.is_error() && result.error() != Audio::AudioQueue::QueueStatus::Empty); } - void set_paused(bool paused) - { - m_paused = paused; - } - - int get_remaining_samples() const { return m_remaining_samples; } - int get_played_samples() const { return m_played_samples; } - int get_playing_buffer() const - { - if (m_current) - return m_current->id(); - return -1; - } + void set_paused(bool paused) { m_paused = paused; } FadingProperty& volume() { return m_volume; } double volume() const { return m_volume; } @@ -97,11 +82,10 @@ public: void set_muted(bool muted) { m_muted = muted; } private: - RefPtr m_current; - Queue> m_queue; - int m_position { 0 }; - int m_remaining_samples { 0 }; - int m_played_samples { 0 }; + OwnPtr m_buffer; + Array m_current_audio_chunk; + size_t m_in_chunk_location; + bool m_paused { false }; bool m_muted { false }; diff --git a/Userland/Utilities/abench.cpp b/Userland/Utilities/abench.cpp index ea1d7af4ad..2cc05ffae4 100644 --- a/Userland/Utilities/abench.cpp +++ b/Userland/Utilities/abench.cpp @@ -51,9 +51,9 @@ ErrorOr serenity_main(Main::Arguments args) auto elapsed = static_cast(sample_timer.elapsed()); total_loader_time += static_cast(elapsed); if (!samples.is_error()) { - remaining_samples -= samples.value()->sample_count(); - total_loaded_samples += samples.value()->sample_count(); - if (samples.value()->sample_count() == 0) + remaining_samples -= samples.value().size(); + total_loaded_samples += samples.value().size(); + if (samples.value().size() == 0) break; } else { warnln("Error while loading audio: {}", samples.error().description); diff --git a/Userland/Utilities/aplay.cpp b/Userland/Utilities/aplay.cpp index 97bc1ce7f6..aae55d3456 100644 --- a/Userland/Utilities/aplay.cpp +++ b/Userland/Utilities/aplay.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -21,7 +22,7 @@ constexpr size_t LOAD_CHUNK_SIZE = 128 * KiB; ErrorOr serenity_main(Main::Arguments arguments) { - TRY(Core::System::pledge("stdio rpath sendfd unix")); + TRY(Core::System::pledge("stdio rpath sendfd unix thread")); char const* path = nullptr; bool should_loop = false; @@ -47,7 +48,7 @@ ErrorOr serenity_main(Main::Arguments arguments) } auto loader = maybe_loader.release_value(); - TRY(Core::System::pledge("stdio sendfd")); + TRY(Core::System::pledge("stdio sendfd thread")); outln("\033[34;1m Playing\033[0m: {}", path); outln("\033[34;1m Format\033[0m: {} {} Hz, {}-bit, {}", @@ -57,46 +58,50 @@ ErrorOr serenity_main(Main::Arguments arguments) loader->num_channels() == 1 ? "Mono" : "Stereo"); out("\033[34;1mProgress\033[0m: \033[s"); - auto resampler = Audio::ResampleHelper(loader->sample_rate(), audio_client->get_sample_rate()); + auto resampler = Audio::ResampleHelper(loader->sample_rate(), audio_client->get_sample_rate()); // If we're downsampling, we need to appropriately load more samples at once. size_t const load_size = static_cast(LOAD_CHUNK_SIZE * static_cast(loader->sample_rate()) / static_cast(audio_client->get_sample_rate())); // We assume that the loader can load samples at at least 2x speed (testing confirms 9x-12x for FLAC, 14x for WAV). // Therefore, when the server-side buffer can only play as long as the time it takes us to load a chunk, // we give it new data. - int const min_buffer_size = load_size / 2; + unsigned const min_buffer_size = load_size / 2; + + auto print_playback_update = [&]() { + out("\033[u"); + if (show_sample_progress) { + out("{}/{}", audio_client->total_played_samples(), loader->total_samples()); + } else { + auto playing_seconds = static_cast(floor(static_cast(audio_client->total_played_samples()) / static_cast(loader->sample_rate()))); + auto playing_minutes = playing_seconds / 60; + auto playing_seconds_of_minute = playing_seconds % 60; + + auto total_seconds = static_cast(floor(static_cast(loader->total_samples()) / static_cast(loader->sample_rate()))); + auto total_minutes = total_seconds / 60; + auto total_seconds_of_minute = total_seconds % 60; + + auto remaining_seconds = total_seconds - playing_seconds; + auto remaining_minutes = remaining_seconds / 60; + auto remaining_seconds_of_minute = remaining_seconds % 60; + + out("\033[1m{:02d}:{:02d}\033[0m [{}{:02d}:{:02d}] -- {:02d}:{:02d}", + playing_minutes, playing_seconds_of_minute, + remaining_seconds == 0 ? " " : "-", + remaining_minutes, remaining_seconds_of_minute, + total_minutes, total_seconds_of_minute); + } + fflush(stdout); + }; for (;;) { auto samples = loader->get_more_samples(load_size); if (!samples.is_error()) { - if (samples.value()->sample_count() > 0) { + if (samples.value().size() > 0) { + print_playback_update(); // We can read and enqueue more samples - out("\033[u"); - if (show_sample_progress) { - out("{}/{}", loader->loaded_samples(), loader->total_samples()); - } else { - auto playing_seconds = static_cast(floor(static_cast(loader->loaded_samples()) / static_cast(loader->sample_rate()))); - auto playing_minutes = playing_seconds / 60; - auto playing_seconds_of_minute = playing_seconds % 60; - - auto total_seconds = static_cast(floor(static_cast(loader->total_samples()) / static_cast(loader->sample_rate()))); - auto total_minutes = total_seconds / 60; - auto total_seconds_of_minute = total_seconds % 60; - - auto remaining_seconds = total_seconds - playing_seconds; - auto remaining_minutes = remaining_seconds / 60; - auto remaining_seconds_of_minute = remaining_seconds % 60; - - out("\033[1m{:02d}:{:02d}\033[0m [{}{:02d}:{:02d}] -- {:02d}:{:02d}", - playing_minutes, playing_seconds_of_minute, - remaining_seconds == 0 ? " " : "-", - remaining_minutes, remaining_seconds_of_minute, - total_minutes, total_seconds_of_minute); - } - fflush(stdout); resampler.reset(); - auto resampled_samples = TRY(Audio::resample_buffer(resampler, *samples.value())); - audio_client->async_enqueue(*resampled_samples); + auto resampled_samples = resampler.resample(move(samples.value())); + TRY(audio_client->async_enqueue(move(resampled_samples))); } else if (should_loop) { // We're done: now loop auto result = loader->reset(); @@ -104,13 +109,14 @@ ErrorOr serenity_main(Main::Arguments arguments) outln(); outln("Error while resetting: {} (at {:x})", result.error().description, result.error().index); } - } else if (samples.value()->sample_count() == 0 && audio_client->get_remaining_samples() == 0) { + } else if (samples.value().size() == 0 && audio_client->remaining_samples() == 0) { // We're done and the server is done break; } - while (audio_client->get_remaining_samples() > min_buffer_size) { + while (audio_client->remaining_samples() > min_buffer_size) { // The server has enough data for now - sleep(1); + print_playback_update(); + usleep(1'000'000 / 10); } } else { outln(); diff --git a/Userland/Utilities/asctl.cpp b/Userland/Utilities/asctl.cpp index 9bbf07edcc..0d41f03e53 100644 --- a/Userland/Utilities/asctl.cpp +++ b/Userland/Utilities/asctl.cpp @@ -30,6 +30,7 @@ ErrorOr serenity_main(Main::Arguments arguments) { Core::EventLoop loop; auto audio_client = TRY(Audio::ConnectionFromClient::try_create()); + audio_client->async_pause_playback(); String command = String::empty(); Vector command_arguments; @@ -43,7 +44,7 @@ ErrorOr serenity_main(Main::Arguments arguments) args_parser.parse(arguments); TRY(Core::System::unveil(nullptr, nullptr)); - TRY(Core::System::pledge("stdio rpath wpath recvfd")); + TRY(Core::System::pledge("stdio rpath wpath recvfd thread")); if (command.equals_ignoring_case("get") || command == "g") { // Get variables