From 111fd1e5fe0dc769b3866c0cf00613da914eb467 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?kleines=20Filmr=C3=B6llchen?= Date: Thu, 6 Jul 2023 20:10:17 +0200 Subject: [PATCH] LibAudio: Optimize FlacLoader by avoiding allocations - Pre-allocate and reuse sample decompression buffers. In many FLAC files, the amount of samples per frame is either constant or the largest frame will be hit within the first couple of frames. Also, during audio output, we need to move and combine the samples from the decompression buffers into the final output buffers anyways. Avoiding the reallocation of these large buffers provides an improvement from 16x to 18x decode speed on strongly compressed but otherwise usual input. - Leave a FIXME for a similar improvement that can be made in the residual decoder. - Pre-allocate audio chunks if frame size is known. - Use reasonable inline capacities in several places where we know the maximum or usual capacity needed. --- Userland/Libraries/LibAudio/FlacLoader.cpp | 59 ++++++++++++---------- Userland/Libraries/LibAudio/FlacLoader.h | 8 ++- Userland/Libraries/LibAudio/MultiChannel.h | 48 ++++++++++++------ 3 files changed, 69 insertions(+), 46 deletions(-) diff --git a/Userland/Libraries/LibAudio/FlacLoader.cpp b/Userland/Libraries/LibAudio/FlacLoader.cpp index 8826555254..188860a0cc 100644 --- a/Userland/Libraries/LibAudio/FlacLoader.cpp +++ b/Userland/Libraries/LibAudio/FlacLoader.cpp @@ -348,6 +348,10 @@ ErrorOr>, LoaderError> FlacLoaderPlugin::load_chunks(s size_t samples_to_read = min(samples_to_read_from_input, remaining_samples); Vector> frames; + // In this case we can know exactly how many frames we're going to read. + if (is_fixed_blocksize_stream() && m_current_frame.has_value()) + TRY(frames.try_ensure_capacity(samples_to_read / m_current_frame->sample_count + 1)); + size_t sample_index = 0; while (!m_stream->is_eof() && sample_index < samples_to_read) { @@ -445,20 +449,17 @@ LoaderSamples FlacLoaderPlugin::next_frame() }; u8 subframe_count = frame_channel_type_to_channel_count(channel_type); - Vector> current_subframes; - current_subframes.ensure_capacity(subframe_count); + TRY(m_subframe_buffers.try_resize_and_keep_capacity(subframe_count)); float sample_rescale = 1 / static_cast(1 << (m_current_frame->bit_depth - 1)); dbgln_if(AFLACLOADER_DEBUG, "Samples will be rescaled from {} bits: factor {:.8f}", m_current_frame->bit_depth, sample_rescale); 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)); + auto& subframe_samples = m_subframe_buffers[i]; + subframe_samples.clear_with_capacity(); + TRY(parse_subframe(subframe_samples, new_subframe, bit_stream)); VERIFY(subframe_samples.size() == m_current_frame->sample_count); - FixedArray scaled_samples = TRY(FixedArray::create(m_current_frame->sample_count)); - for (size_t i = 0; i < m_current_frame->sample_count; ++i) - scaled_samples[i] = static_cast(subframe_samples[i]) * sample_rescale; - current_subframes.unchecked_append(move(scaled_samples)); } // 11.2. Overview ("The audio data is composed of...") @@ -480,7 +481,7 @@ LoaderSamples FlacLoaderPlugin::next_frame() case FlacFrameChannelType::Surround5p1: case FlacFrameChannelType::Surround6p1: case FlacFrameChannelType::Surround7p1: { - auto new_samples = TRY(downmix_surround_to_stereo>(move(current_subframes))); + auto new_samples = TRY(downmix_surround_to_stereo>(m_subframe_buffers, sample_rescale)); samples.swap(new_samples); break; } @@ -490,8 +491,8 @@ LoaderSamples FlacLoaderPlugin::next_frame() // channels are left (0) and side (1) for (size_t i = 0; i < m_current_frame->sample_count; ++i) { // right = left - side - samples[i] = { current_subframes[0][i], - current_subframes[0][i] - current_subframes[1][i] }; + samples[i] = { static_cast(m_subframe_buffers[0][i]) * sample_rescale, + static_cast(m_subframe_buffers[0][i] - m_subframe_buffers[1][i]) * sample_rescale }; } break; } @@ -501,8 +502,8 @@ LoaderSamples FlacLoaderPlugin::next_frame() // channels are side (0) and right (1) for (size_t i = 0; i < m_current_frame->sample_count; ++i) { // left = right + side - samples[i] = { current_subframes[1][i] + current_subframes[0][i], - current_subframes[1][i] }; + samples[i] = { static_cast(m_subframe_buffers[1][i] + m_subframe_buffers[0][i]) * sample_rescale, + static_cast(m_subframe_buffers[1][i]) * sample_rescale }; } break; } @@ -510,13 +511,13 @@ LoaderSamples FlacLoaderPlugin::next_frame() auto new_samples = TRY(FixedArray::create(m_current_frame->sample_count)); samples.swap(new_samples); // channels are mid (0) and side (1) - for (size_t i = 0; i < current_subframes[0].size(); ++i) { - float mid = current_subframes[0][i]; - float side = current_subframes[1][i]; + for (size_t i = 0; i < m_subframe_buffers[0].size(); ++i) { + i64 mid = m_subframe_buffers[0][i]; + i64 side = m_subframe_buffers[1][i]; mid *= 2; // prevent integer division errors - samples[i] = { (mid + side) * .5f, - (mid - side) * .5f }; + samples[i] = { static_cast(mid + side) * .5f * sample_rescale, + static_cast(mid - side) * .5f * sample_rescale }; } break; } @@ -683,9 +684,9 @@ ErrorOr FlacLoaderPlugin::next_subframe_header( }; } -ErrorOr, LoaderError> FlacLoaderPlugin::parse_subframe(FlacSubframeHeader& subframe_header, BigEndianInputBitStream& bit_input) +ErrorOr FlacLoaderPlugin::parse_subframe(Vector& samples, FlacSubframeHeader& subframe_header, BigEndianInputBitStream& bit_input) { - Vector samples; + TRY(samples.try_ensure_capacity(m_current_frame->sample_count)); switch (subframe_header.type) { case FlacSubframeType::Constant: { @@ -693,7 +694,6 @@ ErrorOr, LoaderError> FlacLoaderPlugin::parse_subframe(FlacSubframeH u64 constant_value = TRY(bit_input.read_bits(subframe_header.bits_per_sample - subframe_header.wasted_bits_per_sample)); dbgln_if(AFLACLOADER_DEBUG, "Constant subframe: {}", constant_value); - samples.ensure_capacity(m_current_frame->sample_count); VERIFY(subframe_header.bits_per_sample - subframe_header.wasted_bits_per_sample != 0); i64 constant = sign_extend(static_cast(constant_value), subframe_header.bits_per_sample - subframe_header.wasted_bits_per_sample); for (u64 i = 0; i < m_current_frame->sample_count; ++i) { @@ -713,7 +713,7 @@ ErrorOr, LoaderError> FlacLoaderPlugin::parse_subframe(FlacSubframeH } case FlacSubframeType::LPC: { dbgln_if(AFLACLOADER_DEBUG, "Custom LPC subframe order {}", subframe_header.order); - samples = TRY(decode_custom_lpc(subframe_header, bit_input)); + TRY(decode_custom_lpc(samples, subframe_header, bit_input)); break; } default: @@ -725,11 +725,13 @@ ErrorOr, LoaderError> FlacLoaderPlugin::parse_subframe(FlacSubframeH } // Resamplers VERIFY that the sample rate is non-zero. - if (m_current_frame->sample_rate == 0 || m_sample_rate == 0) - return samples; + if (m_current_frame->sample_rate == 0 || m_sample_rate == 0 + || m_current_frame->sample_rate == m_sample_rate) + return {}; ResampleHelper resampler(m_current_frame->sample_rate, m_sample_rate); - return resampler.resample(samples); + samples = resampler.resample(samples); + return {}; } // 11.29. SUBFRAME_VERBATIM @@ -751,13 +753,12 @@ ErrorOr, LoaderError> FlacLoaderPlugin::decode_verbatim(FlacSubframe // 11.28. SUBFRAME_LPC // Decode a subframe encoded with a custom linear predictor coding, i.e. the subframe provides the polynomial order and coefficients -ErrorOr, LoaderError> FlacLoaderPlugin::decode_custom_lpc(FlacSubframeHeader& subframe, BigEndianInputBitStream& bit_input) +ErrorOr FlacLoaderPlugin::decode_custom_lpc(Vector& decoded, FlacSubframeHeader& subframe, BigEndianInputBitStream& bit_input) { // LPC must provide at least as many samples as its order. if (subframe.order > m_current_frame->sample_count) return LoaderError { LoaderError::Category::Format, static_cast(m_current_sample_or_frame), "Too small frame for LPC order" }; - Vector decoded; decoded.ensure_capacity(m_current_frame->sample_count); VERIFY(subframe.bits_per_sample - subframe.wasted_bits_per_sample != 0); @@ -777,7 +778,7 @@ ErrorOr, LoaderError> FlacLoaderPlugin::decode_custom_lpc(FlacSubfra // shift needed on the data (signed!) i8 lpc_shift = static_cast(sign_extend(TRY(bit_input.read_bits(5)), 5)); - Vector coefficients; + Vector coefficients; coefficients.ensure_capacity(subframe.order); // read coefficients for (auto i = 0; i < subframe.order; ++i) { @@ -805,7 +806,7 @@ ErrorOr, LoaderError> FlacLoaderPlugin::decode_custom_lpc(FlacSubfra decoded[i] += sample >> lpc_shift; } - return decoded; + return {}; } // 11.27. SUBFRAME_FIXED @@ -896,6 +897,7 @@ MaybeLoaderError FlacLoaderPlugin::decode_residual(Vector& decoded, FlacSub // 11.30.2. RESIDUAL_CODING_METHOD_PARTITIONED_EXP_GOLOMB // decode a single Rice partition with four bits for the order k for (size_t i = 0; i < partitions; ++i) { + // FIXME: Write into the decode buffer directly. auto rice_partition = TRY(decode_rice_partition(4, partitions, i, subframe, bit_input)); decoded.extend(move(rice_partition)); } @@ -903,6 +905,7 @@ MaybeLoaderError FlacLoaderPlugin::decode_residual(Vector& decoded, FlacSub // 11.30.3. RESIDUAL_CODING_METHOD_PARTITIONED_EXP_GOLOMB2 // five bits equivalent for (size_t i = 0; i < partitions; ++i) { + // FIXME: Write into the decode buffer directly. auto rice_partition = TRY(decode_rice_partition(5, partitions, i, subframe, bit_input)); decoded.extend(move(rice_partition)); } diff --git a/Userland/Libraries/LibAudio/FlacLoader.h b/Userland/Libraries/LibAudio/FlacLoader.h index cdd7b4bf27..f134340617 100644 --- a/Userland/Libraries/LibAudio/FlacLoader.h +++ b/Userland/Libraries/LibAudio/FlacLoader.h @@ -69,11 +69,11 @@ private: // Helper of next_frame that fetches a sub frame's header ErrorOr next_subframe_header(BigEndianInputBitStream& bit_input, u8 channel_index); // Helper of next_frame that decompresses a subframe - ErrorOr, LoaderError> parse_subframe(FlacSubframeHeader& subframe_header, BigEndianInputBitStream& bit_input); + ErrorOr parse_subframe(Vector& samples, FlacSubframeHeader& subframe_header, BigEndianInputBitStream& bit_input); // Subframe-internal data decoders (heavy lifting) ErrorOr, LoaderError> decode_fixed_lpc(FlacSubframeHeader& subframe, BigEndianInputBitStream& bit_input); ErrorOr, LoaderError> decode_verbatim(FlacSubframeHeader& subframe, BigEndianInputBitStream& bit_input); - ErrorOr, LoaderError> decode_custom_lpc(FlacSubframeHeader& subframe, BigEndianInputBitStream& bit_input); + ErrorOr decode_custom_lpc(Vector& decoded, FlacSubframeHeader& subframe, BigEndianInputBitStream& bit_input); MaybeLoaderError decode_residual(Vector& decoded, FlacSubframeHeader& subframe, BigEndianInputBitStream& bit_input); // decode a single rice partition that has its own rice parameter ALWAYS_INLINE ErrorOr, LoaderError> decode_rice_partition(u8 partition_type, u32 partitions, u32 partition_index, FlacSubframeHeader& subframe, BigEndianInputBitStream& bit_input); @@ -110,6 +110,10 @@ private: Optional m_current_frame; u64 m_current_sample_or_frame { 0 }; SeekTable m_seektable; + + // Keep around a few temporary buffers whose allocated space can be reused. + // This is an empirical optimization since allocations and deallocations take a lot of time in the decoder. + mutable Vector, 2> m_subframe_buffers; }; } diff --git a/Userland/Libraries/LibAudio/MultiChannel.h b/Userland/Libraries/LibAudio/MultiChannel.h index f9debeb4d0..c2fe280815 100644 --- a/Userland/Libraries/LibAudio/MultiChannel.h +++ b/Userland/Libraries/LibAudio/MultiChannel.h @@ -21,8 +21,9 @@ namespace Audio { // 6 channels = front left/right, center, LFE, back left/right // 7 channels = front left/right, center, LFE, back center, side left/right // 8 channels = front left/right, center, LFE, back left/right, side left/right -template ChannelType, ArrayLike InputType> -ErrorOr> downmix_surround_to_stereo(InputType input) +// Additionally, performs sample rescaling to go from integer samples to floating-point samples. +template ChannelType, ArrayLike InputType> +ErrorOr> downmix_surround_to_stereo(InputType const& input, float sample_scale_factor) { if (input.size() == 0) return Error::from_string_view("Cannot resample from 0 channels"sv); @@ -38,43 +39,58 @@ ErrorOr> downmix_surround_to_stereo(InputType input) switch (channel_count) { case 1: for (auto i = 0u; i < sample_count; ++i) - output[i] = Sample { input[0][i] }; + output[i] = Sample { input[0][i] * sample_scale_factor }; break; case 2: for (auto i = 0u; i < sample_count; ++i) - output[i] = Sample { input[0][i], input[1][i] }; + output[i] = Sample { + input[0][i] * sample_scale_factor, + input[1][i] * sample_scale_factor + }; break; case 3: for (auto i = 0u; i < sample_count; ++i) - output[i] = Sample { input[0][i] + input[2][i], - input[1][i] + input[2][i] }; + output[i] = Sample { + input[0][i] * sample_scale_factor + input[2][i] * sample_scale_factor, + input[1][i] * sample_scale_factor + input[2][i] * sample_scale_factor + }; break; case 4: for (auto i = 0u; i < sample_count; ++i) - output[i] = Sample { input[0][i] + input[2][i], - input[1][i] + input[3][i] }; + output[i] = Sample { + input[0][i] * sample_scale_factor + input[2][i] * sample_scale_factor, + input[1][i] * sample_scale_factor + input[3][i] * sample_scale_factor + }; break; case 5: for (auto i = 0u; i < sample_count; ++i) - output[i] = Sample { input[0][i] + input[3][i] + input[2][i], - input[1][i] + input[4][i] + input[2][i] }; + output[i] = Sample { + input[0][i] * sample_scale_factor + input[3][i] * sample_scale_factor + input[2][i] * sample_scale_factor, + input[1][i] * sample_scale_factor + input[4][i] * sample_scale_factor + input[2][i] * sample_scale_factor + }; break; case 6: for (auto i = 0u; i < sample_count; ++i) { - output[i] = Sample { input[0][i] + input[4][i] + input[2][i] + input[3][i], - input[1][i] + input[5][i] + input[2][i] + input[3][i] }; + output[i] = Sample { + input[0][i] * sample_scale_factor + input[4][i] * sample_scale_factor + input[2][i] * sample_scale_factor + input[3][i] * sample_scale_factor, + input[1][i] * sample_scale_factor + input[5][i] * sample_scale_factor + input[2][i] * sample_scale_factor + input[3][i] * sample_scale_factor + }; } break; case 7: for (auto i = 0u; i < sample_count; ++i) { - output[i] = Sample { input[0][i] + input[5][i] + input[2][i] + input[3][i] + input[4][i], - input[1][i] + input[6][i] + input[2][i] + input[3][i] + input[4][i] }; + output[i] = Sample { + input[0][i] * sample_scale_factor + input[5][i] * sample_scale_factor + input[2][i] * sample_scale_factor + input[3][i] * sample_scale_factor + input[4][i] * sample_scale_factor, + input[1][i] * sample_scale_factor + input[6][i] * sample_scale_factor + input[2][i] * sample_scale_factor + input[3][i] * sample_scale_factor + input[4][i] * sample_scale_factor + }; } break; case 8: for (auto i = 0u; i < sample_count; ++i) { - output[i] = Sample { input[0][i] + input[4][i] + input[6][i] + input[2][i] + input[3][i], - input[1][i] + input[5][i] + input[7][i] + input[2][i] + input[3][i] }; + output[i] = Sample { + input[0][i] * sample_scale_factor + input[4][i] * sample_scale_factor + input[6][i] * sample_scale_factor + input[2][i] * sample_scale_factor + input[3][i] * sample_scale_factor, + input[1][i] * sample_scale_factor + input[5][i] * sample_scale_factor + input[7][i] * sample_scale_factor + input[2][i] * sample_scale_factor + input[3][i] * sample_scale_factor + }; } break; default: