From b48badc3b63b29d232fad4c9b044ff619b1538c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?kleines=20Filmr=C3=B6llchen?= Date: Fri, 17 Dec 2021 18:42:36 +0100 Subject: [PATCH] LibAudio: Remove frame-wise copys from FlacLoader Previously, FlacLoader would read the data for each frame into a separate vector, which are then combined via extend() in the end. This incurs an avoidable copy per frame. By having the next_frame() function write into a given Span, there's only one vector allocated per call to get_more_samples(). This increases performance by at least 100% realtime, as measured by abench, from about 1200%-1300% to (usually) 1400% on complex test files. --- Userland/Libraries/LibAudio/FlacLoader.cpp | 65 +++++++++++++--------- Userland/Libraries/LibAudio/FlacLoader.h | 7 ++- 2 files changed, 42 insertions(+), 30 deletions(-) diff --git a/Userland/Libraries/LibAudio/FlacLoader.cpp b/Userland/Libraries/LibAudio/FlacLoader.cpp index a4f4108435d..4aa3cdf0291 100644 --- a/Userland/Libraries/LibAudio/FlacLoader.cpp +++ b/Userland/Libraries/LibAudio/FlacLoader.cpp @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -13,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -190,41 +192,41 @@ MaybeLoaderError FlacLoaderPlugin::seek(const int position) LoaderSamples FlacLoaderPlugin::get_more_samples(size_t max_bytes_to_read_from_input) { - Vector samples; ssize_t remaining_samples = static_cast(m_total_samples - m_loaded_samples); if (remaining_samples <= 0) return Buffer::create_empty(); size_t samples_to_read = min(max_bytes_to_read_from_input, remaining_samples); - samples.ensure_capacity(samples_to_read); - while (samples_to_read > 0) { - if (!m_current_frame.has_value()) - TRY(next_frame()); + auto samples = FixedArray(samples_to_read); + size_t sample_index = 0; - // Do a full vector extend if possible - if (m_current_frame_data.size() <= samples_to_read) { - samples_to_read -= m_current_frame_data.size(); - samples.extend(move(m_current_frame_data)); - m_current_frame_data.clear(); - m_current_frame.clear(); - } else { - samples.unchecked_append(m_current_frame_data.data(), samples_to_read); - m_current_frame_data.remove(0, samples_to_read); - if (m_current_frame_data.size() == 0) { - m_current_frame.clear(); - } - samples_to_read = 0; - } + if (m_unread_data.size() > 0) { + size_t to_transfer = min(m_unread_data.size(), samples_to_read); + dbgln_if(AFLACLOADER_DEBUG, "Reading {} samples from unread sample buffer (size {})", to_transfer, m_unread_data.size()); + AK::TypedTransfer::move(samples.data(), m_unread_data.data(), to_transfer); + if (to_transfer < m_unread_data.size()) + m_unread_data.remove(0, to_transfer); + else + m_unread_data.clear_with_capacity(); + + sample_index += to_transfer; } - m_loaded_samples += samples.size(); + while (sample_index < samples_to_read) { + TRY(next_frame(samples.span().slice(sample_index))); + sample_index += m_current_frame->sample_count; + if (m_stream->handle_any_error()) + return LoaderError { LoaderError::Category::IO, m_loaded_samples, "Unknown I/O error" }; + } + + m_loaded_samples += sample_index; auto maybe_buffer = Buffer::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(); } -MaybeLoaderError FlacLoaderPlugin::next_frame() +MaybeLoaderError FlacLoaderPlugin::next_frame(Span target_vector) { #define FLAC_VERIFY(check, category, msg) \ do { \ @@ -293,13 +295,14 @@ MaybeLoaderError FlacLoaderPlugin::next_frame() for (u8 i = 0; i < subframe_count; ++i) { FlacSubframeHeader new_subframe = TRY(next_subframe_header(bit_stream, i)); Vector subframe_samples = TRY(parse_subframe(new_subframe, bit_stream)); - current_subframes.append(move(subframe_samples)); + current_subframes.unchecked_append(move(subframe_samples)); } bit_stream.align_to_byte_boundary(); // TODO: check checksum, see above [[maybe_unused]] u16 footer_checksum = static_cast(bit_stream.read_bits_big_endian(16)); + dbgln_if(AFLACLOADER_DEBUG, "Subframe footer checksum: {}", footer_checksum); Vector left; Vector right; @@ -352,17 +355,25 @@ MaybeLoaderError FlacLoaderPlugin::next_frame() break; } - VERIFY(left.size() == right.size()); + VERIFY(left.size() == right.size() && left.size() == m_current_frame->sample_count); double sample_rescale = static_cast(1 << (pcm_bits_per_sample(m_current_frame->bit_depth) - 1)); dbgln_if(AFLACLOADER_DEBUG, "Sample rescaled from {} bits: factor {:.1f}", pcm_bits_per_sample(m_current_frame->bit_depth), sample_rescale); - m_current_frame_data.clear_with_capacity(); - m_current_frame_data.ensure_capacity(left.size()); // zip together channels - for (size_t i = 0; i < left.size(); ++i) { + auto samples_to_directly_copy = min(target_vector.size(), m_current_frame->sample_count); + for (size_t i = 0; i < samples_to_directly_copy; ++i) { Sample frame = { left[i] / sample_rescale, right[i] / sample_rescale }; - m_current_frame_data.unchecked_append(frame); + target_vector[i] = frame; + } + // move superflous data into the class buffer instead + auto result = m_unread_data.try_grow_capacity(m_current_frame->sample_count - samples_to_directly_copy); + if (result.is_error()) + return LoaderError { LoaderError::Category::Internal, static_cast(samples_to_directly_copy + m_current_sample_or_frame), "Couldn't allocate sample buffer for superflous data" }; + + for (size_t i = samples_to_directly_copy; i < m_current_frame->sample_count; ++i) { + Sample frame = { left[i] / sample_rescale, right[i] / sample_rescale }; + m_unread_data.unchecked_append(frame); } return {}; diff --git a/Userland/Libraries/LibAudio/FlacLoader.h b/Userland/Libraries/LibAudio/FlacLoader.h index 41e124b60ea..12842b5f971 100644 --- a/Userland/Libraries/LibAudio/FlacLoader.h +++ b/Userland/Libraries/LibAudio/FlacLoader.h @@ -111,8 +111,8 @@ private: // Either returns the metadata block or sets error message. // Additionally, increments m_data_start_location past the read meta block. ErrorOr next_meta_block(InputBitStream& bit_input); - // Fetches and sets the next FLAC frame - MaybeLoaderError next_frame(); + // Fetches and writes the next FLAC frame + MaybeLoaderError next_frame(Span); // Helper of next_frame that fetches a sub frame's header ErrorOr next_subframe_header(InputBitStream& bit_input, u8 channel_index); // Helper of next_frame that decompresses a subframe @@ -151,7 +151,8 @@ private: u64 m_data_start_location { 0 }; OwnPtr> m_stream; Optional m_current_frame; - Vector m_current_frame_data; + // Whatever the last get_more_samples() call couldn't return gets stored here. + Vector m_unread_data; u64 m_current_sample_or_frame { 0 }; };