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: