diff options
author | kleines Filmröllchen <malu.bertsch@gmail.com> | 2021-11-27 17:00:19 +0100 |
---|---|---|
committer | Brian Gianforcaro <b.gianfo@gmail.com> | 2021-11-28 13:33:51 -0800 |
commit | 96d02a3e753b53533d2aaf50e2dd2d92738f0e29 (patch) | |
tree | 9824e7c67eda20af029054380108ca96e5a9697a | |
parent | ec8bd8116d4683ae51897e8e49c62823bfdd9005 (diff) | |
download | serenity-96d02a3e753b53533d2aaf50e2dd2d92738f0e29.zip |
LibAudio: New error propagation API in Loader and Buffer
Previously, a libc-like out-of-line error information was used in the
loader and its plugins. Now, all functions that may fail to do their job
return some sort of Result. The universally-used error type ist the new
LoaderError, which can contain information about the general error
category (such as file format, I/O, unimplemented features), an error
description, and location information, such as file index or sample
index.
Additionally, the loader plugins try to do as little work as possible in
their constructors. Right after being constructed, a user should call
initialize() and check the errors returned from there. (This is done
transparently by Loader itself.) If a constructor caused an error, the
call to initialize should check and return it immediately.
This opportunity was used to rework a lot of the internal error
propagation in both loader classes, especially FlacLoader. Therefore, a
couple of other refactorings may have sneaked in as well.
The adoption of LibAudio users is minimal. Piano's adoption is not
important, as the code will receive major refactoring in the near future
anyways. SoundPlayer's adoption is also less important, as changes to
refactor it are in the works as well. aplay's adoption is the best and
may serve as an example for other users. It also includes new buffering
behavior.
Buffer also gets some attention, making it OOM-safe and thereby also
propagating its errors to the user.
-rw-r--r-- | Meta/Lagom/Fuzzers/FuzzFlacLoader.cpp | 11 | ||||
-rw-r--r-- | Meta/Lagom/Fuzzers/FuzzWAVLoader.cpp | 12 | ||||
-rw-r--r-- | Userland/Applications/Piano/AudioPlayerLoop.cpp | 6 | ||||
-rw-r--r-- | Userland/Applications/Piano/Track.cpp | 2 | ||||
-rw-r--r-- | Userland/Applications/SoundPlayer/PlaybackManager.cpp | 13 | ||||
-rw-r--r-- | Userland/Applications/SoundPlayer/Player.cpp | 7 | ||||
-rw-r--r-- | Userland/Applications/SoundPlayer/Playlist.cpp | 4 | ||||
-rw-r--r-- | Userland/Libraries/LibAudio/Buffer.cpp | 6 | ||||
-rw-r--r-- | Userland/Libraries/LibAudio/Buffer.h | 21 | ||||
-rw-r--r-- | Userland/Libraries/LibAudio/FlacLoader.cpp | 327 | ||||
-rw-r--r-- | Userland/Libraries/LibAudio/FlacLoader.h | 39 | ||||
-rw-r--r-- | Userland/Libraries/LibAudio/Loader.cpp | 44 | ||||
-rw-r--r-- | Userland/Libraries/LibAudio/Loader.h | 66 | ||||
-rw-r--r-- | Userland/Libraries/LibAudio/LoaderError.h | 52 | ||||
-rw-r--r-- | Userland/Libraries/LibAudio/WavLoader.cpp | 114 | ||||
-rw-r--r-- | Userland/Libraries/LibAudio/WavLoader.h | 16 | ||||
-rw-r--r-- | Userland/Services/AudioServer/ClientConnection.cpp | 3 | ||||
-rw-r--r-- | Userland/Utilities/aplay.cpp | 64 | ||||
-rw-r--r-- | Userland/Utilities/asctl.cpp | 4 |
19 files changed, 410 insertions, 401 deletions
diff --git a/Meta/Lagom/Fuzzers/FuzzFlacLoader.cpp b/Meta/Lagom/Fuzzers/FuzzFlacLoader.cpp index eafc31d3fe..7c32717c47 100644 --- a/Meta/Lagom/Fuzzers/FuzzFlacLoader.cpp +++ b/Meta/Lagom/Fuzzers/FuzzFlacLoader.cpp @@ -13,11 +13,16 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) auto flac_data = ByteBuffer::copy(data, size).release_value(); auto flac = make<Audio::FlacLoaderPlugin>(flac_data); - if (!flac->sniff()) + if (flac->initialize().is_error()) return 1; - while (flac->get_more_samples()) - ; + for (;;) { + auto samples = flac->get_more_samples(); + if (samples.is_error()) + return 2; + if (samples.value()->sample_count() > 0) + break; + } return 0; } diff --git a/Meta/Lagom/Fuzzers/FuzzWAVLoader.cpp b/Meta/Lagom/Fuzzers/FuzzWAVLoader.cpp index 519fd82d8f..fd2b99d9d6 100644 --- a/Meta/Lagom/Fuzzers/FuzzWAVLoader.cpp +++ b/Meta/Lagom/Fuzzers/FuzzWAVLoader.cpp @@ -13,11 +13,13 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) auto wav_data = ByteBuffer::copy(data, size).release_value(); auto wav = make<Audio::WavLoaderPlugin>(wav_data); - if (!wav->sniff()) - return 1; - - while (wav->get_more_samples()) - ; + for (;;) { + auto samples = wav->get_more_samples(); + if (samples.is_error()) + return 2; + if (samples.value()->sample_count() > 0) + break; + } return 0; } diff --git a/Userland/Applications/Piano/AudioPlayerLoop.cpp b/Userland/Applications/Piano/AudioPlayerLoop.cpp index 85a2768974..e226d241f4 100644 --- a/Userland/Applications/Piano/AudioPlayerLoop.cpp +++ b/Userland/Applications/Piano/AudioPlayerLoop.cpp @@ -18,7 +18,8 @@ static NonnullRefPtr<Audio::Buffer> music_samples_to_buffer(Array<Sample, sample Audio::Sample frame = { sample.left / (double)NumericLimits<i16>::max(), sample.right / (double)NumericLimits<i16>::max() }; frames.unchecked_append(frame); } - return Audio::Buffer::create_with_samples(frames); + // FIXME: Handle OOM better. + return MUST(Audio::Buffer::create_with_samples(frames)); } AudioPlayerLoop::AudioPlayerLoop(TrackManager& track_manager, bool& need_to_write_wav, Audio::WavWriter& wav_writer) @@ -42,7 +43,8 @@ void AudioPlayerLoop::enqueue_audio() { m_track_manager.fill_buffer(m_buffer); NonnullRefPtr<Audio::Buffer> audio_buffer = music_samples_to_buffer(m_buffer); - audio_buffer = Audio::resample_buffer(m_resampler.value(), *audio_buffer); + // FIXME: Handle OOM better. + audio_buffer = MUST(Audio::resample_buffer(m_resampler.value(), *audio_buffer)); m_audio_client->async_enqueue(audio_buffer); // FIXME: This should be done somewhere else. diff --git a/Userland/Applications/Piano/Track.cpp b/Userland/Applications/Piano/Track.cpp index 0a3f176cdf..ad1be3dd95 100644 --- a/Userland/Applications/Piano/Track.cpp +++ b/Userland/Applications/Piano/Track.cpp @@ -7,7 +7,7 @@ */ #include "Track.h" -#include "Music.h" +#include <AK/Forward.h> #include <AK/Math.h> #include <AK/NonnullRefPtr.h> #include <AK/NumericLimits.h> diff --git a/Userland/Applications/SoundPlayer/PlaybackManager.cpp b/Userland/Applications/SoundPlayer/PlaybackManager.cpp index 2d8592dd71..f9253ddf1d 100644 --- a/Userland/Applications/SoundPlayer/PlaybackManager.cpp +++ b/Userland/Applications/SoundPlayer/PlaybackManager.cpp @@ -46,7 +46,7 @@ void PlaybackManager::stop() m_current_buffer = nullptr; if (m_loader) - m_loader->reset(); + (void)m_loader->reset(); } void PlaybackManager::play() @@ -70,7 +70,8 @@ void PlaybackManager::seek(const int position) m_connection->clear_buffer(true); m_current_buffer = nullptr; - m_loader->seek(position); + + [[maybe_unused]] auto result = m_loader->seek(position); if (!paused_state) set_paused(false); @@ -117,11 +118,13 @@ void PlaybackManager::next_buffer() } if (audio_server_remaining_samples < m_device_samples_per_buffer) { - m_current_buffer = m_loader->get_more_samples(m_source_buffer_size_bytes); - if (m_current_buffer) { + auto maybe_buffer = m_loader->get_more_samples(m_source_buffer_size_bytes); + if (!maybe_buffer.is_error()) { + m_current_buffer = maybe_buffer.release_value(); VERIFY(m_resampler.has_value()); m_resampler->reset(); - m_current_buffer = Audio::resample_buffer(m_resampler.value(), *m_current_buffer); + // FIXME: Handle OOM better. + m_current_buffer = MUST(Audio::resample_buffer(m_resampler.value(), *m_current_buffer)); m_connection->enqueue(*m_current_buffer); } } diff --git a/Userland/Applications/SoundPlayer/Player.cpp b/Userland/Applications/SoundPlayer/Player.cpp index e4f31aa648..d9ffbbb86d 100644 --- a/Userland/Applications/SoundPlayer/Player.cpp +++ b/Userland/Applications/SoundPlayer/Player.cpp @@ -53,11 +53,12 @@ void Player::play_file_path(String const& path) return; } - NonnullRefPtr<Audio::Loader> loader = Audio::Loader::create(path); - if (loader->has_error()) { - audio_load_error(path, loader->error_string()); + auto maybe_loader = Audio::Loader::create(path); + if (maybe_loader.is_error()) { + audio_load_error(path, maybe_loader.error().description); return; } + auto loader = maybe_loader.value(); m_loaded_filename = path; diff --git a/Userland/Applications/SoundPlayer/Playlist.cpp b/Userland/Applications/SoundPlayer/Playlist.cpp index b20490b2dc..96f09f8001 100644 --- a/Userland/Applications/SoundPlayer/Playlist.cpp +++ b/Userland/Applications/SoundPlayer/Playlist.cpp @@ -52,8 +52,8 @@ void Playlist::try_fill_missing_info(Vector<M3UEntry>& entries, StringView path) if (!entry.extended_info->track_length_in_seconds.has_value()) { //TODO: Implement embedded metadata extractor for other audio formats - if (auto reader = Audio::Loader::create(entry.path); !reader->has_error()) - entry.extended_info->track_length_in_seconds = reader->total_samples() / reader->sample_rate(); + if (auto reader = Audio::Loader::create(entry.path); !reader.is_error()) + entry.extended_info->track_length_in_seconds = reader.value()->total_samples() / reader.value()->sample_rate(); } //TODO: Implement a metadata parser for the uncomfortably numerous popular embedded metadata formats diff --git a/Userland/Libraries/LibAudio/Buffer.cpp b/Userland/Libraries/LibAudio/Buffer.cpp index c58cfa5f72..8a087f4e09 100644 --- a/Userland/Libraries/LibAudio/Buffer.cpp +++ b/Userland/Libraries/LibAudio/Buffer.cpp @@ -122,13 +122,13 @@ static double read_norm_sample_8(InputMemoryStream& stream) return double(sample) / NumericLimits<u8>::max(); } -NonnullRefPtr<Buffer> Buffer::from_pcm_data(ReadonlyBytes data, int num_channels, PcmSampleFormat sample_format) +ErrorOr<NonnullRefPtr<Buffer>> Buffer::from_pcm_data(ReadonlyBytes data, int num_channels, PcmSampleFormat sample_format) { InputMemoryStream stream { data }; return from_pcm_stream(stream, num_channels, sample_format, data.size() / (pcm_bits_per_sample(sample_format) / 8)); } -NonnullRefPtr<Buffer> Buffer::from_pcm_stream(InputMemoryStream& stream, int num_channels, PcmSampleFormat sample_format, int num_samples) +ErrorOr<NonnullRefPtr<Buffer>> Buffer::from_pcm_stream(InputMemoryStream& stream, int num_channels, PcmSampleFormat sample_format, int num_samples) { Vector<Sample> fdata; fdata.ensure_capacity(num_samples); @@ -189,7 +189,7 @@ Vector<SampleType> ResampleHelper<SampleType>::resample(Vector<SampleType> to_re template Vector<i32> ResampleHelper<i32>::resample(Vector<i32>); template Vector<double> ResampleHelper<double>::resample(Vector<double>); -NonnullRefPtr<Buffer> resample_buffer(ResampleHelper<double>& resampler, Buffer const& to_resample) +ErrorOr<NonnullRefPtr<Buffer>> resample_buffer(ResampleHelper<double>& resampler, Buffer const& to_resample) { Vector<Sample> resampled; resampled.ensure_capacity(to_resample.sample_count() * ceil_div(resampler.source(), resampler.target())); diff --git a/Userland/Libraries/LibAudio/Buffer.h b/Userland/Libraries/LibAudio/Buffer.h index 8f9e8457af..12245342f1 100644 --- a/Userland/Libraries/LibAudio/Buffer.h +++ b/Userland/Libraries/LibAudio/Buffer.h @@ -8,10 +8,14 @@ #pragma once #include <AK/ByteBuffer.h> +#include <AK/Error.h> #include <AK/MemoryStream.h> +#include <AK/NonnullRefPtr.h> +#include <AK/RefPtr.h> #include <AK/String.h> #include <AK/Types.h> #include <AK/Vector.h> +#include <AK/kmalloc.h> #include <LibAudio/Sample.h> #include <LibCore/AnonymousBuffer.h> #include <string.h> @@ -67,19 +71,20 @@ private: // A buffer of audio samples. class Buffer : public RefCounted<Buffer> { public: - static NonnullRefPtr<Buffer> from_pcm_data(ReadonlyBytes data, int num_channels, PcmSampleFormat sample_format); - static NonnullRefPtr<Buffer> from_pcm_stream(InputMemoryStream& stream, int num_channels, PcmSampleFormat sample_format, int num_samples); - static NonnullRefPtr<Buffer> create_with_samples(Vector<Sample>&& samples) + static ErrorOr<NonnullRefPtr<Buffer>> from_pcm_data(ReadonlyBytes data, int num_channels, PcmSampleFormat sample_format); + static ErrorOr<NonnullRefPtr<Buffer>> from_pcm_stream(InputMemoryStream& stream, int num_channels, PcmSampleFormat sample_format, int num_samples); + static ErrorOr<NonnullRefPtr<Buffer>> create_with_samples(Vector<Sample>&& samples) { - return adopt_ref(*new Buffer(move(samples))); + return adopt_nonnull_ref_or_enomem(new (nothrow) Buffer(move(samples))); } - static NonnullRefPtr<Buffer> create_with_anonymous_buffer(Core::AnonymousBuffer buffer, i32 buffer_id, int sample_count) + static ErrorOr<NonnullRefPtr<Buffer>> create_with_anonymous_buffer(Core::AnonymousBuffer buffer, i32 buffer_id, int sample_count) { - return adopt_ref(*new Buffer(move(buffer), buffer_id, sample_count)); + return adopt_nonnull_ref_or_enomem(new (nothrow) Buffer(move(buffer), buffer_id, sample_count)); } static NonnullRefPtr<Buffer> create_empty() { - return adopt_ref(*new Buffer({})); + // If we can't allocate an empty buffer, things are in a very bad state. + return MUST(adopt_nonnull_ref_or_enomem(new (nothrow) Buffer({}))); } const Sample* samples() const { return (const Sample*)data(); } @@ -115,6 +120,6 @@ private: }; // This only works for double resamplers, and therefore cannot be part of the class -NonnullRefPtr<Buffer> resample_buffer(ResampleHelper<double>& resampler, Buffer const& to_resample); +ErrorOr<NonnullRefPtr<Buffer>> resample_buffer(ResampleHelper<double>& resampler, Buffer const& to_resample); } diff --git a/Userland/Libraries/LibAudio/FlacLoader.cpp b/Userland/Libraries/LibAudio/FlacLoader.cpp index c38a3b2c5d..d0d40d9ff4 100644 --- a/Userland/Libraries/LibAudio/FlacLoader.cpp +++ b/Userland/Libraries/LibAudio/FlacLoader.cpp @@ -4,19 +4,20 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include "FlacLoader.h" -#include "Buffer.h" -#include <AK/BitStream.h> #include <AK/Debug.h> #include <AK/FlyString.h> #include <AK/Format.h> #include <AK/Math.h> #include <AK/ScopeGuard.h> -#include <AK/Stream.h> #include <AK/String.h> #include <AK/StringBuilder.h> +#include <AK/Try.h> +#include <AK/UFixedBigInt.h> +#include <LibAudio/Buffer.h> +#include <LibAudio/FlacLoader.h> +#include <LibAudio/FlacTypes.h> +#include <LibAudio/LoaderError.h> #include <LibCore/File.h> -#include <LibCore/FileStream.h> namespace Audio { @@ -24,101 +25,80 @@ FlacLoaderPlugin::FlacLoaderPlugin(StringView path) : m_file(Core::File::construct(path)) { if (!m_file->open(Core::OpenMode::ReadOnly)) { - m_error_string = String::formatted("Can't open file: {}", m_file->error_string()); + m_error = LoaderError { String::formatted("Can't open file: {}", m_file->error_string()) }; return; } auto maybe_stream = Core::InputFileStream::open_buffered(path); if (maybe_stream.is_error()) { - m_error_string = "Can't open file stream"; + m_error = LoaderError { "Can't open file stream" }; return; } m_stream = make<FlacInputStream>(maybe_stream.release_value()); - if (!m_stream) { - m_error_string = "Can't open file stream"; - return; - } - - m_valid = parse_header(); - if (!m_valid) - return; - reset(); - if (!m_valid) - return; + if (!m_stream) + m_error = LoaderError { "Can't open file stream" }; } FlacLoaderPlugin::FlacLoaderPlugin(const ByteBuffer& buffer) { m_stream = make<FlacInputStream>(InputMemoryStream(buffer)); - if (!m_stream) { - m_error_string = String::formatted("Can't open memory stream"); - return; - } - - m_valid = parse_header(); - if (!m_valid) - return; - reset(); - if (!m_valid) - return; + if (!m_stream) + m_error = LoaderError { "Can't open memory stream" }; } -bool FlacLoaderPlugin::sniff() +MaybeLoaderError FlacLoaderPlugin::initialize() { - return m_valid; + if (m_error.has_value()) + return m_error.release_value(); + + TRY(parse_header()); + TRY(reset()); + return {}; } -bool FlacLoaderPlugin::parse_header() +MaybeLoaderError FlacLoaderPlugin::parse_header() { - bool ok = true; - InputBitStream bit_input = [&]() -> InputBitStream { if (m_file) { return InputBitStream(m_stream->get<Buffered<Core::InputFileStream>>()); } return InputBitStream(m_stream->get<InputMemoryStream>()); }(); - ScopeGuard clear_bit_input_errors([&bit_input] { bit_input.handle_any_error(); }); - -#define CHECK_OK(msg) \ - do { \ - if (!ok) { \ - m_stream->handle_any_error(); \ - m_error_string = String::formatted("Parsing failed: {}", msg); \ - return {}; \ - } \ + ScopeGuard handle_all_errors([&bit_input, this] { + m_stream->handle_any_error(); + bit_input.handle_any_error(); + }); + + // A mixture of VERIFY and the non-crashing TRY(). +#define FLAC_VERIFY(check, category, msg) \ + do { \ + if (!(check)) { \ + return LoaderError { category, static_cast<size_t>(m_data_start_location), String::formatted("FLAC header: {}", msg) }; \ + } \ } while (0) // Magic number u32 flac = static_cast<u32>(bit_input.read_bits_big_endian(32)); m_data_start_location += 4; - ok = ok && flac == 0x664C6143; // "flaC" - CHECK_OK("FLAC magic number"); + FLAC_VERIFY(flac == 0x664C6143, LoaderError::Category::Format, "Magic number must be 'flaC'"); // "flaC" // Receive the streaminfo block - FlacRawMetadataBlock streaminfo = next_meta_block(bit_input); - // next_meta_block sets the error string if something goes wrong - ok = ok && m_error_string.is_empty(); - CHECK_OK(m_error_string); - ok = ok && (streaminfo.type == FlacMetadataBlockType::STREAMINFO); - CHECK_OK("First block type"); + auto streaminfo = TRY(next_meta_block(bit_input)); + FLAC_VERIFY(streaminfo.type == FlacMetadataBlockType::STREAMINFO, LoaderError::Category::Format, "First block must be STREAMINFO"); InputMemoryStream streaminfo_data_memory(streaminfo.data.bytes()); InputBitStream streaminfo_data(streaminfo_data_memory); ScopeGuard clear_streaminfo_errors([&streaminfo_data] { streaminfo_data.handle_any_error(); }); // STREAMINFO block m_min_block_size = static_cast<u16>(streaminfo_data.read_bits_big_endian(16)); - ok = ok && (m_min_block_size >= 16); - CHECK_OK("Minimum block size"); + FLAC_VERIFY(m_min_block_size >= 16, LoaderError::Category::Format, "Minimum block size must be 16"); m_max_block_size = static_cast<u16>(streaminfo_data.read_bits_big_endian(16)); - ok = ok && (m_max_block_size >= 16); - CHECK_OK("Maximum block size"); + FLAC_VERIFY(m_max_block_size >= 16, LoaderError::Category::Format, "Maximum block size"); m_min_frame_size = static_cast<u32>(streaminfo_data.read_bits_big_endian(24)); m_max_frame_size = static_cast<u32>(streaminfo_data.read_bits_big_endian(24)); m_sample_rate = static_cast<u32>(streaminfo_data.read_bits_big_endian(20)); - ok = ok && (m_sample_rate <= 655350); - CHECK_OK("Sample rate"); - m_num_channels = static_cast<u8>(streaminfo_data.read_bits_big_endian(3)) + 1; // 0 ^= one channel + FLAC_VERIFY(m_sample_rate <= 655350, LoaderError::Category::Format, "Sample rate"); + m_num_channels = static_cast<u8>(streaminfo_data.read_bits_big_endian(3)) + 1; // 0 = one channel u8 bits_per_sample = static_cast<u8>(streaminfo_data.read_bits_big_endian(5)) + 1; if (bits_per_sample == 8) { @@ -131,19 +111,16 @@ bool FlacLoaderPlugin::parse_header() } else if (bits_per_sample == 32) { m_sample_format = PcmSampleFormat::Int32; } else { - ok = false; - CHECK_OK("Sample bit depth"); + FLAC_VERIFY(false, LoaderError::Category::Format, "Sample bit depth invalid"); } m_total_samples = static_cast<u64>(streaminfo_data.read_bits_big_endian(36)); - ok = ok && (m_total_samples > 0); - CHECK_OK("Number of samples"); + FLAC_VERIFY(m_total_samples > 0, LoaderError::Category::Format, "Number of samples is zero"); // Parse checksum into a buffer first - Array<u8, 128 / 8> md5_checksum; - auto md5_bytes_read = streaminfo_data.read(md5_checksum); - ok = ok && (md5_bytes_read == md5_checksum.size()); - CHECK_OK("MD5 Checksum"); - md5_checksum.span().copy_to({ m_md5_checksum, sizeof(m_md5_checksum) }); + [[maybe_unused]] u128 md5_checksum; + auto md5_bytes_read = streaminfo_data.read(md5_checksum.bytes()); + FLAC_VERIFY(md5_bytes_read == md5_checksum.my_size(), LoaderError::Category::IO, "MD5 Checksum size"); + md5_checksum.bytes().copy_to({ m_md5_checksum, sizeof(m_md5_checksum) }); // Parse other blocks // TODO: For a simple first implementation, all other blocks are skipped as allowed by the FLAC specification. @@ -152,38 +129,23 @@ bool FlacLoaderPlugin::parse_header() [[maybe_unused]] u16 total_meta_blocks = meta_blocks_parsed; FlacRawMetadataBlock block = streaminfo; while (!block.is_last_block) { - block = next_meta_block(bit_input); + block = TRY(next_meta_block(bit_input)); ++total_meta_blocks; - ok = ok && m_error_string.is_empty(); - CHECK_OK(m_error_string); } - if (m_stream->handle_any_error()) { - m_error_string = "Parsing failed: Stream"; - return false; - } + FLAC_VERIFY(!m_stream->handle_any_error(), LoaderError::Category::IO, "Stream"); - if constexpr (AFLACLOADER_DEBUG) { - // HACK: u128 should be able to format itself - StringBuilder checksum_string; - for (unsigned int i = 0; i < md5_checksum.size(); ++i) { - checksum_string.appendff("{:0X}", md5_checksum[i]); - } - dbgln("Parsed FLAC header: blocksize {}-{}{}, framesize {}-{}, {}Hz, {}bit, {} channels, {} samples total ({:.2f}s), MD5 {}, data start at {:x} bytes, {} headers total (skipped {})", m_min_block_size, m_max_block_size, is_fixed_blocksize_stream() ? " (constant)" : "", m_min_frame_size, m_max_frame_size, m_sample_rate, pcm_bits_per_sample(m_sample_format), m_num_channels, m_total_samples, static_cast<double>(m_total_samples) / static_cast<double>(m_sample_rate), checksum_string.to_string(), m_data_start_location, total_meta_blocks, total_meta_blocks - meta_blocks_parsed); - } + dbgln_if(AFLACLOADER_DEBUG, "Parsed FLAC header: blocksize {}-{}{}, framesize {}-{}, {}Hz, {}bit, {} channels, {} samples total ({:.2f}s), MD5 {}, data start at {:x} bytes, {} headers total (skipped {})", m_min_block_size, m_max_block_size, is_fixed_blocksize_stream() ? " (constant)" : "", m_min_frame_size, m_max_frame_size, m_sample_rate, pcm_bits_per_sample(m_sample_format), m_num_channels, m_total_samples, static_cast<double>(m_total_samples) / static_cast<double>(m_sample_rate), md5_checksum, m_data_start_location, total_meta_blocks, total_meta_blocks - meta_blocks_parsed); - return true; -#undef CHECK_OK + return {}; } -FlacRawMetadataBlock FlacLoaderPlugin::next_meta_block(InputBitStream& bit_input) +ErrorOr<FlacRawMetadataBlock, LoaderError> FlacLoaderPlugin::next_meta_block(InputBitStream& bit_input) { -#define CHECK_IO_ERROR() \ - do { \ - if (bit_input.handle_any_error()) { \ - m_error_string = "Read error"; \ - return FlacRawMetadataBlock {}; \ - } \ +#define CHECK_IO_ERROR() \ + do { \ + if (bit_input.handle_any_error()) \ + return LoaderError { LoaderError::Category::IO, "Read error" }; \ } while (0) bool is_last_block = bit_input.read_bit_big_endian(); @@ -191,20 +153,14 @@ FlacRawMetadataBlock FlacLoaderPlugin::next_meta_block(InputBitStream& bit_input // The block type enum constants agree with the specification FlacMetadataBlockType type = (FlacMetadataBlockType)bit_input.read_bits_big_endian(7); CHECK_IO_ERROR(); - if (type == FlacMetadataBlockType::INVALID) { - m_error_string = "Invalid metadata block"; - return FlacRawMetadataBlock {}; - } m_data_start_location += 1; + FLAC_VERIFY(type != FlacMetadataBlockType::INVALID, LoaderError::Category::Format, "Invalid metadata block"); u32 block_length = static_cast<u32>(bit_input.read_bits_big_endian(24)); m_data_start_location += 3; CHECK_IO_ERROR(); auto block_data_result = ByteBuffer::create_uninitialized(block_length); - if (!block_data_result.has_value()) { - m_error_string = "Out of memory"; - return FlacRawMetadataBlock {}; - } + FLAC_VERIFY(block_data_result.has_value(), LoaderError::Category::IO, "Out of memory"); auto block_data = block_data_result.release_value(); // Reads exactly the bytes necessary into the Bytes container bit_input.read(block_data); @@ -219,38 +175,34 @@ FlacRawMetadataBlock FlacLoaderPlugin::next_meta_block(InputBitStream& bit_input #undef CHECK_IO_ERROR } +#undef FLAC_VERIFY -void FlacLoaderPlugin::reset() +MaybeLoaderError FlacLoaderPlugin::reset() { - seek(m_data_start_location); + TRY(seek(m_data_start_location)); m_current_frame.clear(); + return {}; } -void FlacLoaderPlugin::seek(const int position) +MaybeLoaderError FlacLoaderPlugin::seek(const int position) { - if (!m_stream->seek(position)) { - m_error_string = String::formatted("Invalid seek position {}", position); - m_valid = false; - } + if (!m_stream->seek(position)) + return LoaderError { LoaderError::IO, m_loaded_samples, String::formatted("Invalid seek position {}", position) }; + return {}; } -RefPtr<Buffer> FlacLoaderPlugin::get_more_samples(size_t max_bytes_to_read_from_input) +LoaderSamples FlacLoaderPlugin::get_more_samples(size_t max_bytes_to_read_from_input) { Vector<Sample> samples; ssize_t remaining_samples = static_cast<ssize_t>(m_total_samples - m_loaded_samples); - if (remaining_samples <= 0) { - return nullptr; - } + if (remaining_samples <= 0) + return Buffer::create_empty(); size_t samples_to_read = min(max_bytes_to_read_from_input, remaining_samples); while (samples_to_read > 0) { - if (!m_current_frame.has_value()) { - next_frame(); - if (!m_error_string.is_empty()) { - m_error_string = String::formatted("Frame parsing error: {}", m_error_string); - return nullptr; - } - } + if (!m_current_frame.has_value()) + TRY(next_frame()); + samples.append(m_current_frame_data.take_first()); if (m_current_frame_data.is_empty()) { m_current_frame.clear(); @@ -259,62 +211,43 @@ RefPtr<Buffer> FlacLoaderPlugin::get_more_samples(size_t max_bytes_to_read_from_ } m_loaded_samples += samples.size(); - return Buffer::create_with_samples(move(samples)); + 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(); } -void FlacLoaderPlugin::next_frame() +MaybeLoaderError FlacLoaderPlugin::next_frame() { - bool ok = true; - InputBitStream bit_stream = m_stream->bit_stream(); -#define CHECK_OK(msg) \ - do { \ - if (!ok) { \ - m_error_string = String::formatted("Frame parsing failed: {}", msg); \ - bit_stream.align_to_byte_boundary(); \ - bit_stream.handle_any_error(); \ - dbgln_if(AFLACLOADER_DEBUG, "Crash in FLAC loader: next bytes are {:x}", bit_stream.read_bits_big_endian(32)); \ - return; \ - } \ - } while (0) - -#define CHECK_ERROR_STRING \ - do { \ - if (!m_error_string.is_null() && !m_error_string.is_empty()) { \ - ok = false; \ - CHECK_OK(m_error_string); \ - } \ +#define FLAC_VERIFY(check, category, msg) \ + do { \ + if (!(check)) { \ + return LoaderError { category, static_cast<size_t>(m_current_sample_or_frame), String::formatted("FLAC header: {}", msg) }; \ + } \ } while (0) + InputBitStream bit_stream = m_stream->bit_stream(); // TODO: Check the CRC-16 checksum (and others) by keeping track of read data // FLAC frame sync code starts header u16 sync_code = static_cast<u16>(bit_stream.read_bits_big_endian(14)); - ok = ok && (sync_code == 0b11111111111110); - CHECK_OK("Sync code"); + FLAC_VERIFY(sync_code == 0b11111111111110, LoaderError::Category::Format, "Sync code"); bool reserved_bit = bit_stream.read_bit_big_endian(); - ok = ok && (reserved_bit == 0); - CHECK_OK("Reserved frame header bit"); + FLAC_VERIFY(reserved_bit == 0, LoaderError::Category::Format, "Reserved frame header bit"); [[maybe_unused]] bool blocking_strategy = bit_stream.read_bit_big_endian(); - u32 sample_count = convert_sample_count_code(static_cast<u8>(bit_stream.read_bits_big_endian(4))); - CHECK_ERROR_STRING; + u32 sample_count = TRY(convert_sample_count_code(static_cast<u8>(bit_stream.read_bits_big_endian(4)))); - u32 frame_sample_rate = convert_sample_rate_code(static_cast<u8>(bit_stream.read_bits_big_endian(4))); - CHECK_ERROR_STRING; + u32 frame_sample_rate = TRY(convert_sample_rate_code(static_cast<u8>(bit_stream.read_bits_big_endian(4)))); u8 channel_type_num = static_cast<u8>(bit_stream.read_bits_big_endian(4)); - if (channel_type_num >= 0b1011) { - ok = false; - CHECK_OK("Channel assignment"); - } + FLAC_VERIFY(channel_type_num < 0b1011, LoaderError::Format, "Channel assignment"); FlacFrameChannelType channel_type = (FlacFrameChannelType)channel_type_num; - PcmSampleFormat bit_depth = convert_bit_depth_code(static_cast<u8>(bit_stream.read_bits_big_endian(3))); - CHECK_ERROR_STRING; + PcmSampleFormat bit_depth = TRY(convert_bit_depth_code(static_cast<u8>(bit_stream.read_bits_big_endian(3)))); reserved_bit = bit_stream.read_bit_big_endian(); - ok = ok && (reserved_bit == 0); - CHECK_OK("Reserved frame header end bit"); + FLAC_VERIFY(reserved_bit == 0, LoaderError::Category::Format, "Reserved frame header end bit"); // FIXME: sample number can be 8-56 bits, frame number can be 8-48 bits m_current_sample_or_frame = read_utf8_char(bit_stream); @@ -351,10 +284,8 @@ void FlacLoaderPlugin::next_frame() current_subframes.ensure_capacity(subframe_count); for (u8 i = 0; i < subframe_count; ++i) { - FlacSubframeHeader new_subframe = next_subframe_header(bit_stream, i); - CHECK_ERROR_STRING; - Vector<i32> subframe_samples = parse_subframe(new_subframe, bit_stream); - CHECK_ERROR_STRING; + FlacSubframeHeader new_subframe = TRY(next_subframe_header(bit_stream, i)); + Vector<i32> subframe_samples = TRY(parse_subframe(new_subframe, bit_stream)); current_subframes.append(move(subframe_samples)); } @@ -427,17 +358,16 @@ void FlacLoaderPlugin::next_frame() m_current_frame_data.unchecked_append(frame); } -#undef CHECK_OK -#undef CHECK_ERROR_STRING + return {}; +#undef FLAC_VERIFY } -u32 FlacLoaderPlugin::convert_sample_count_code(u8 sample_count_code) +ErrorOr<u32, LoaderError> FlacLoaderPlugin::convert_sample_count_code(u8 sample_count_code) { // single codes switch (sample_count_code) { case 0: - m_error_string = "Reserved block size"; - return 0; + return LoaderError { LoaderError::Category::Format, static_cast<size_t>(m_current_sample_or_frame), "Reserved block size" }; case 1: return 192; case 6: @@ -451,7 +381,7 @@ u32 FlacLoaderPlugin::convert_sample_count_code(u8 sample_count_code) return 256 * AK::exp2(sample_count_code - 8); } -u32 FlacLoaderPlugin::convert_sample_rate_code(u8 sample_rate_code) +ErrorOr<u32, LoaderError> FlacLoaderPlugin::convert_sample_rate_code(u8 sample_rate_code) { switch (sample_rate_code) { case 0: @@ -485,12 +415,11 @@ u32 FlacLoaderPlugin::convert_sample_rate_code(u8 sample_rate_code) case 14: return FLAC_SAMPLERATE_AT_END_OF_HEADER_16X10; default: - m_error_string = "Invalid sample rate code"; - return 0; + return LoaderError { LoaderError::Category::Format, static_cast<size_t>(m_current_sample_or_frame), "Invalid sample rate code" }; } } -PcmSampleFormat FlacLoaderPlugin::convert_bit_depth_code(u8 bit_depth_code) +ErrorOr<PcmSampleFormat, LoaderError> FlacLoaderPlugin::convert_bit_depth_code(u8 bit_depth_code) { switch (bit_depth_code) { case 0: @@ -503,11 +432,9 @@ PcmSampleFormat FlacLoaderPlugin::convert_bit_depth_code(u8 bit_depth_code) return PcmSampleFormat::Int24; case 3: case 7: - m_error_string = "Reserved sample size"; - return PcmSampleFormat::Float64; + return LoaderError { LoaderError::Category::Format, static_cast<size_t>(m_current_sample_or_frame), "Reserved sample size" }; default: - m_error_string = String::formatted("Unsupported sample size {}", bit_depth_code); - return PcmSampleFormat::Float64; + return LoaderError { LoaderError::Category::Format, static_cast<size_t>(m_current_sample_or_frame), String::formatted("Unsupported sample size {}", bit_depth_code) }; } } @@ -518,7 +445,7 @@ u8 frame_channel_type_to_channel_count(FlacFrameChannelType channel_type) return 2; } -FlacSubframeHeader FlacLoaderPlugin::next_subframe_header(InputBitStream& bit_stream, u8 channel_index) +ErrorOr<FlacSubframeHeader, LoaderError> FlacLoaderPlugin::next_subframe_header(InputBitStream& bit_stream, u8 channel_index) { u8 bits_per_sample = static_cast<u16>(pcm_bits_per_sample(m_current_frame->bit_depth)); @@ -541,17 +468,13 @@ FlacSubframeHeader FlacLoaderPlugin::next_subframe_header(InputBitStream& bit_st } // zero-bit padding - if (bit_stream.read_bit_big_endian() != 0) { - m_error_string = "Zero bit padding"; - return {}; - }; + if (bit_stream.read_bit_big_endian() != 0) + return LoaderError { LoaderError::Category::Format, static_cast<size_t>(m_current_sample_or_frame), "Zero bit padding" }; // subframe type (encoding) u8 subframe_code = static_cast<u8>(bit_stream.read_bits_big_endian(6)); - if ((subframe_code >= 0b000010 && subframe_code <= 0b000111) || (subframe_code > 0b001100 && subframe_code < 0b100000)) { - m_error_string = "Subframe type"; - return {}; - } + if ((subframe_code >= 0b000010 && subframe_code <= 0b000111) || (subframe_code > 0b001100 && subframe_code < 0b100000)) + return LoaderError { LoaderError::Category::Format, static_cast<size_t>(m_current_sample_or_frame), "Subframe type" }; FlacSubframeType subframe_type; u8 order = 0; @@ -586,7 +509,7 @@ FlacSubframeHeader FlacLoaderPlugin::next_subframe_header(InputBitStream& bit_st }; } -Vector<i32> FlacLoaderPlugin::parse_subframe(FlacSubframeHeader& subframe_header, InputBitStream& bit_input) +ErrorOr<Vector<i32>, LoaderError> FlacLoaderPlugin::parse_subframe(FlacSubframeHeader& subframe_header, InputBitStream& bit_input) { Vector<i32> samples; @@ -605,25 +528,21 @@ Vector<i32> FlacLoaderPlugin::parse_subframe(FlacSubframeHeader& subframe_header } case FlacSubframeType::Fixed: { dbgln_if(AFLACLOADER_DEBUG, "Fixed LPC subframe order {}", subframe_header.order); - samples = decode_fixed_lpc(subframe_header, bit_input); + samples = TRY(decode_fixed_lpc(subframe_header, bit_input)); break; } case FlacSubframeType::Verbatim: { dbgln_if(AFLACLOADER_DEBUG, "Verbatim subframe"); - samples = decode_verbatim(subframe_header, bit_input); + samples = TRY(decode_verbatim(subframe_header, bit_input)); break; } case FlacSubframeType::LPC: { dbgln_if(AFLACLOADER_DEBUG, "Custom LPC subframe order {}", subframe_header.order); - samples = decode_custom_lpc(subframe_header, bit_input); + samples = TRY(decode_custom_lpc(subframe_header, bit_input)); break; } default: - m_error_string = "Unhandled FLAC subframe type"; - return {}; - } - if (!m_error_string.is_empty()) { - return {}; + return LoaderError { LoaderError::Category::Unimplemented, static_cast<size_t>(m_current_sample_or_frame), "Unhandled FLAC subframe type" }; } for (size_t i = 0; i < samples.size(); ++i) { @@ -635,7 +554,7 @@ Vector<i32> FlacLoaderPlugin::parse_subframe(FlacSubframeHeader& subframe_header } // Decode a subframe that isn't actually encoded, usually seen in random data -Vector<i32> FlacLoaderPlugin::decode_verbatim(FlacSubframeHeader& subframe, InputBitStream& bit_input) +ErrorOr<Vector<i32>, LoaderError> FlacLoaderPlugin::decode_verbatim(FlacSubframeHeader& subframe, InputBitStream& bit_input) { Vector<i32> decoded; decoded.ensure_capacity(m_current_frame->sample_count); @@ -651,7 +570,7 @@ Vector<i32> FlacLoaderPlugin::decode_verbatim(FlacSubframeHeader& subframe, Inpu } // Decode a subframe encoded with a custom linear predictor coding, i.e. the subframe provides the polynomial order and coefficients -Vector<i32> FlacLoaderPlugin::decode_custom_lpc(FlacSubframeHeader& subframe, InputBitStream& bit_input) +ErrorOr<Vector<i32>, LoaderError> FlacLoaderPlugin::decode_custom_lpc(FlacSubframeHeader& subframe, InputBitStream& bit_input) { Vector<i32> decoded; decoded.ensure_capacity(m_current_frame->sample_count); @@ -666,10 +585,8 @@ Vector<i32> FlacLoaderPlugin::decode_custom_lpc(FlacSubframeHeader& subframe, In // precision of the coefficients u8 lpc_precision = static_cast<u8>(bit_input.read_bits_big_endian(4)); - if (lpc_precision == 0b1111) { - m_error_string = "Invalid linear predictor coefficient precision"; - return {}; - } + if (lpc_precision == 0b1111) + return LoaderError { LoaderError::Category::Format, static_cast<size_t>(m_current_sample_or_frame), "Invalid linear predictor coefficient precision" }; lpc_precision += 1; // shift needed on the data (signed!) @@ -687,7 +604,7 @@ Vector<i32> FlacLoaderPlugin::decode_custom_lpc(FlacSubframeHeader& subframe, In dbgln_if(AFLACLOADER_DEBUG, "{}-bit {} shift coefficients: {}", lpc_precision, lpc_shift, coefficients); // decode residual - decoded = decode_residual(decoded, subframe, bit_input); + decoded = TRY(decode_residual(decoded, subframe, bit_input)); // approximate the waveform with the predictor for (size_t i = subframe.order; i < m_current_frame->sample_count; ++i) { @@ -707,7 +624,7 @@ Vector<i32> FlacLoaderPlugin::decode_custom_lpc(FlacSubframeHeader& subframe, In } // Decode a subframe encoded with one of the fixed linear predictor codings -Vector<i32> FlacLoaderPlugin::decode_fixed_lpc(FlacSubframeHeader& subframe, InputBitStream& bit_input) +ErrorOr<Vector<i32>, LoaderError> FlacLoaderPlugin::decode_fixed_lpc(FlacSubframeHeader& subframe, InputBitStream& bit_input) { Vector<i32> decoded; decoded.ensure_capacity(m_current_frame->sample_count); @@ -720,9 +637,8 @@ Vector<i32> FlacLoaderPlugin::decode_fixed_lpc(FlacSubframeHeader& subframe, Inp subframe.bits_per_sample - subframe.wasted_bits_per_sample)); } - decode_residual(decoded, subframe, bit_input); - if (!m_error_string.is_empty()) - return {}; + TRY(decode_residual(decoded, subframe, bit_input)); + dbgln_if(AFLACLOADER_DEBUG, "decoded length {}, {} order predictor", decoded.size(), subframe.order); switch (subframe.order) { @@ -752,14 +668,13 @@ Vector<i32> FlacLoaderPlugin::decode_fixed_lpc(FlacSubframeHeader& subframe, Inp decoded[i] += 4 * decoded[i - 1] - 6 * decoded[i - 2] + 4 * decoded[i - 3] - decoded[i - 4]; break; default: - m_error_string = String::formatted("Unrecognized predictor order {}", subframe.order); - break; + return LoaderError { LoaderError::Category::Format, static_cast<size_t>(m_current_sample_or_frame), String::formatted("Unrecognized predictor order {}", subframe.order) }; } return decoded; } // Decode the residual, the "error" between the function approximation and the actual audio data -Vector<i32> FlacLoaderPlugin::decode_residual(Vector<i32>& decoded, FlacSubframeHeader& subframe, InputBitStream& bit_input) +ErrorOr<Vector<i32>, LoaderError> FlacLoaderPlugin::decode_residual(Vector<i32>& decoded, FlacSubframeHeader& subframe, InputBitStream& bit_input) { u8 residual_mode = static_cast<u8>(bit_input.read_bits_big_endian(2)); u8 partition_order = static_cast<u8>(bit_input.read_bits_big_endian(4)); @@ -777,10 +692,8 @@ Vector<i32> FlacLoaderPlugin::decode_residual(Vector<i32>& decoded, FlacSubframe auto rice_partition = decode_rice_partition(5, partitions, i, subframe, bit_input); decoded.extend(move(rice_partition)); } - } else { - m_error_string = "Reserved residual coding method"; - return {}; - } + } else + return LoaderError { LoaderError::Category::Format, static_cast<size_t>(m_current_sample_or_frame), "Reserved residual coding method" }; return decoded; } diff --git a/Userland/Libraries/LibAudio/FlacLoader.h b/Userland/Libraries/LibAudio/FlacLoader.h index 8152d48827..af8ee0608c 100644 --- a/Userland/Libraries/LibAudio/FlacLoader.h +++ b/Userland/Libraries/LibAudio/FlacLoader.h @@ -11,6 +11,7 @@ #include "Loader.h" #include <AK/BitStream.h> #include <AK/Buffered.h> +#include <AK/Error.h> #include <AK/Stream.h> #include <AK/Types.h> #include <AK/Variant.h> @@ -81,15 +82,12 @@ public: m_stream->handle_any_error(); } - virtual bool sniff() override; + virtual MaybeLoaderError initialize() override; - virtual bool has_error() override { return !m_error_string.is_null(); } - virtual const String& error_string() override { return m_error_string; } + virtual LoaderSamples get_more_samples(size_t max_bytes_to_read_from_input = 128 * KiB) override; - virtual RefPtr<Buffer> get_more_samples(size_t max_bytes_to_read_from_input = 128 * KiB) override; - - virtual void reset() override; - virtual void seek(const int position) override; + virtual MaybeLoaderError reset() override; + virtual MaybeLoaderError seek(const int position) override; virtual int loaded_samples() override { return static_cast<int>(m_loaded_samples); } virtual int total_samples() override { return static_cast<int>(m_total_samples); } @@ -102,32 +100,31 @@ public: bool sample_count_unknown() const { return m_total_samples == 0; } private: - bool parse_header(); + MaybeLoaderError parse_header(); // Either returns the metadata block or sets error message. // Additionally, increments m_data_start_location past the read meta block. - FlacRawMetadataBlock next_meta_block(InputBitStream& bit_input); + ErrorOr<FlacRawMetadataBlock, LoaderError> next_meta_block(InputBitStream& bit_input); // Fetches and sets the next FLAC frame - void next_frame(); + MaybeLoaderError next_frame(); // Helper of next_frame that fetches a sub frame's header - FlacSubframeHeader next_subframe_header(InputBitStream& bit_input, u8 channel_index); + ErrorOr<FlacSubframeHeader, LoaderError> next_subframe_header(InputBitStream& bit_input, u8 channel_index); // Helper of next_frame that decompresses a subframe - Vector<i32> parse_subframe(FlacSubframeHeader& subframe_header, InputBitStream& bit_input); + ErrorOr<Vector<i32>, LoaderError> parse_subframe(FlacSubframeHeader& subframe_header, InputBitStream& bit_input); // Subframe-internal data decoders (heavy lifting) - Vector<i32> decode_fixed_lpc(FlacSubframeHeader& subframe, InputBitStream& bit_input); - Vector<i32> decode_verbatim(FlacSubframeHeader& subframe, InputBitStream& bit_input); - Vector<i32> decode_custom_lpc(FlacSubframeHeader& subframe, InputBitStream& bit_input); - Vector<i32> decode_residual(Vector<i32>& decoded, FlacSubframeHeader& subframe, InputBitStream& bit_input); + ErrorOr<Vector<i32>, LoaderError> decode_fixed_lpc(FlacSubframeHeader& subframe, InputBitStream& bit_input); + ErrorOr<Vector<i32>, LoaderError> decode_verbatim(FlacSubframeHeader& subframe, InputBitStream& bit_input); + ErrorOr<Vector<i32>, LoaderError> decode_custom_lpc(FlacSubframeHeader& subframe, InputBitStream& bit_input); + ErrorOr<Vector<i32>, LoaderError> decode_residual(Vector<i32>& decoded, FlacSubframeHeader& subframe, InputBitStream& bit_input); // decode a single rice partition that has its own rice parameter ALWAYS_INLINE Vector<i32> decode_rice_partition(u8 partition_type, u32 partitions, u32 partition_index, FlacSubframeHeader& subframe, InputBitStream& bit_input); // Converters for special coding used in frame headers - ALWAYS_INLINE u32 convert_sample_count_code(u8 sample_count_code); - ALWAYS_INLINE u32 convert_sample_rate_code(u8 sample_rate_code); - ALWAYS_INLINE PcmSampleFormat convert_bit_depth_code(u8 bit_depth_code); + ALWAYS_INLINE ErrorOr<u32, LoaderError> convert_sample_count_code(u8 sample_count_code); + ALWAYS_INLINE ErrorOr<u32, LoaderError> convert_sample_rate_code(u8 sample_rate_code); + ALWAYS_INLINE ErrorOr<PcmSampleFormat, LoaderError> convert_bit_depth_code(u8 bit_depth_code); - bool m_valid { false }; RefPtr<Core::File> m_file; - String m_error_string; + Optional<LoaderError> m_error {}; // Data obtained directly from the FLAC metadata: many values have specific bit counts u32 m_sample_rate { 0 }; // 20 bit diff --git a/Userland/Libraries/LibAudio/Loader.cpp b/Userland/Libraries/LibAudio/Loader.cpp index aee5e25962..57d7dd4789 100644 --- a/Userland/Libraries/LibAudio/Loader.cpp +++ b/Userland/Libraries/LibAudio/Loader.cpp @@ -5,32 +5,40 @@ */ #include <LibAudio/FlacLoader.h> +#include <LibAudio/Loader.h> #include <LibAudio/WavLoader.h> namespace Audio { -Loader::Loader(StringView path) +Loader::Loader(NonnullOwnPtr<LoaderPlugin> plugin) + : m_plugin(move(plugin)) { - m_plugin = make<WavLoaderPlugin>(path); - if (m_plugin->sniff()) - return; - m_plugin = make<FlacLoaderPlugin>(path); - if (m_plugin->sniff()) - return; - m_plugin = nullptr; } -Loader::Loader(const ByteBuffer& buffer) +Result<NonnullOwnPtr<LoaderPlugin>, LoaderError> Loader::try_create(StringView path) { - m_plugin = make<WavLoaderPlugin>(buffer); - if (m_plugin->sniff()) - return; - m_plugin = make<FlacLoaderPlugin>(buffer); - if (m_plugin->sniff()) { - dbgln("FLAC sniff successful"); - return; - } - m_plugin = nullptr; + NonnullOwnPtr<LoaderPlugin> plugin = adopt_own(*new WavLoaderPlugin(path)); + auto initstate0 = plugin->initialize(); + if (!initstate0.is_error()) + return plugin; + + plugin = adopt_own(*new FlacLoaderPlugin(path)); + auto initstate1 = plugin->initialize(); + if (!initstate1.is_error()) + return plugin; + + return LoaderError { "No loader plugin available" }; +} + +Result<NonnullOwnPtr<LoaderPlugin>, LoaderError> Loader::try_create(ByteBuffer const& buffer) +{ + NonnullOwnPtr<LoaderPlugin> plugin = adopt_own(*new WavLoaderPlugin(buffer)); + if (auto initstate = plugin->initialize(); !initstate.is_error()) + return plugin; + plugin = adopt_own(*new FlacLoaderPlugin(buffer)); + if (auto initstate = plugin->initialize(); !initstate.is_error()) + return plugin; + return LoaderError { "No loader plugin available" }; } } diff --git a/Userland/Libraries/LibAudio/Loader.h b/Userland/Libraries/LibAudio/Loader.h index 276811e5a6..596dd5d08c 100644 --- a/Userland/Libraries/LibAudio/Loader.h +++ b/Userland/Libraries/LibAudio/Loader.h @@ -7,10 +7,15 @@ #pragma once #include <AK/ByteBuffer.h> +#include <AK/NonnullOwnPtr.h> +#include <AK/NonnullRefPtr.h> #include <AK/RefCounted.h> #include <AK/RefPtr.h> +#include <AK/Result.h> #include <AK/StringView.h> +#include <AK/Try.h> #include <LibAudio/Buffer.h> +#include <LibAudio/LoaderError.h> #include <LibCore/File.h> namespace Audio { @@ -18,20 +23,20 @@ namespace Audio { static const String empty_string = ""; static String no_plugin_error = "No loader plugin available"; +using LoaderSamples = Result<NonnullRefPtr<Buffer>, LoaderError>; +using MaybeLoaderError = Result<void, LoaderError>; + class LoaderPlugin { public: virtual ~LoaderPlugin() { } - virtual bool sniff() = 0; - - virtual bool has_error() { return false; } - virtual const String& error_string() { return empty_string; } + virtual MaybeLoaderError initialize() = 0; - virtual RefPtr<Buffer> get_more_samples(size_t max_bytes_to_read_from_input = 128 * KiB) = 0; + virtual LoaderSamples get_more_samples(size_t max_bytes_to_read_from_input = 128 * KiB) = 0; - virtual void reset() = 0; + virtual MaybeLoaderError reset() = 0; - virtual void seek(const int sample_index) = 0; + virtual MaybeLoaderError seek(const int sample_index) = 0; // total_samples() and loaded_samples() should be independent // of the number of channels. @@ -51,37 +56,28 @@ public: class Loader : public RefCounted<Loader> { public: - static NonnullRefPtr<Loader> create(StringView path) { return adopt_ref(*new Loader(path)); } - static NonnullRefPtr<Loader> create(const ByteBuffer& buffer) { return adopt_ref(*new Loader(buffer)); } - - bool has_error() const { return m_plugin ? m_plugin->has_error() : true; } - const String& error_string() const { return m_plugin ? m_plugin->error_string() : no_plugin_error; } - - RefPtr<Buffer> get_more_samples(size_t max_bytes_to_read_from_input = 128 * KiB) const { return m_plugin ? m_plugin->get_more_samples(max_bytes_to_read_from_input) : nullptr; } - - void reset() const - { - if (m_plugin) - m_plugin->reset(); - } - void seek(const int position) const - { - if (m_plugin) - m_plugin->seek(position); - } - - int loaded_samples() const { return m_plugin ? m_plugin->loaded_samples() : 0; } - int total_samples() const { return m_plugin ? m_plugin->total_samples() : 0; } - u32 sample_rate() const { return m_plugin ? m_plugin->sample_rate() : 0; } - u16 num_channels() const { return m_plugin ? m_plugin->num_channels() : 0; } - u16 bits_per_sample() const { return m_plugin ? pcm_bits_per_sample(m_plugin->pcm_format()) : 0; } - RefPtr<Core::File> file() const { return m_plugin ? m_plugin->file() : nullptr; } + static Result<NonnullRefPtr<Loader>, LoaderError> create(StringView path) { return adopt_ref(*new Loader(TRY(try_create(path)))); } + static Result<NonnullRefPtr<Loader>, LoaderError> create(ByteBuffer const& 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); } + + MaybeLoaderError reset() const { return m_plugin->reset(); } + MaybeLoaderError seek(const int position) const { return m_plugin->seek(position); } + + int loaded_samples() const { return m_plugin->loaded_samples(); } + int total_samples() const { return m_plugin->total_samples(); } + u32 sample_rate() const { return m_plugin->sample_rate(); } + u16 num_channels() const { return m_plugin->num_channels(); } + u16 bits_per_sample() const { return pcm_bits_per_sample(m_plugin->pcm_format()); } + RefPtr<Core::File> file() const { return m_plugin->file(); } private: - explicit Loader(StringView path); - explicit Loader(const ByteBuffer& buffer); + static Result<NonnullOwnPtr<LoaderPlugin>, LoaderError> try_create(StringView path); + static Result<NonnullOwnPtr<LoaderPlugin>, LoaderError> try_create(ByteBuffer const& buffer); + + explicit Loader(NonnullOwnPtr<LoaderPlugin>); - mutable OwnPtr<LoaderPlugin> m_plugin; + mutable NonnullOwnPtr<LoaderPlugin> m_plugin; }; } diff --git a/Userland/Libraries/LibAudio/LoaderError.h b/Userland/Libraries/LibAudio/LoaderError.h new file mode 100644 index 0000000000..12a215af42 --- /dev/null +++ b/Userland/Libraries/LibAudio/LoaderError.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2021, kleines Filmröllchen <malu.bertsch@gmail.com>. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <AK/FlyString.h> + +namespace Audio { + +struct LoaderError { + + enum Category : u32 { + // The error category is unknown. + Unknown = 0, + IO, + // The read file doesn't follow the file format. + Format, + // Equivalent to an ASSERT(), except non-crashing. + Internal, + // The loader encountered something in the format that is not yet implemented. + Unimplemented, + }; + Category category { Unknown }; + // Binary index: where in the file the error occurred. + size_t index { 0 }; + FlyString description { String::empty() }; + + constexpr LoaderError() = default; + LoaderError(Category category, size_t index, FlyString description) + : category(category) + , index(index) + , description(move(description)) + { + } + LoaderError(FlyString description) + : description(move(description)) + { + } + LoaderError(Category category, FlyString description) + : category(category) + , description(move(description)) + { + } + + LoaderError(LoaderError&) = default; + LoaderError(LoaderError&&) = default; +}; + +} diff --git a/Userland/Libraries/LibAudio/WavLoader.cpp b/Userland/Libraries/LibAudio/WavLoader.cpp index 70514f4797..ea34b0c8b0 100644 --- a/Userland/Libraries/LibAudio/WavLoader.cpp +++ b/Userland/Libraries/LibAudio/WavLoader.cpp @@ -10,6 +10,7 @@ #include <AK/Debug.h> #include <AK/NumericLimits.h> #include <AK/OwnPtr.h> +#include <AK/Try.h> #include <LibCore/File.h> #include <LibCore/FileStream.h> @@ -21,43 +22,43 @@ WavLoaderPlugin::WavLoaderPlugin(StringView path) : m_file(Core::File::construct(path)) { if (!m_file->open(Core::OpenMode::ReadOnly)) { - m_error_string = String::formatted("Can't open file: {}", m_file->error_string()); + m_error = LoaderError { String::formatted("Can't open file: {}", m_file->error_string()) }; return; } m_stream = make<Core::InputFileStream>(*m_file); +} - valid = parse_header(); - if (!valid) - return; +MaybeLoaderError WavLoaderPlugin::initialize() +{ + if (m_error.has_value()) + return m_error.release_value(); + TRY(parse_header()); + return {}; } WavLoaderPlugin::WavLoaderPlugin(const ByteBuffer& buffer) { m_stream = make<InputMemoryStream>(buffer); if (!m_stream) { - m_error_string = String::formatted("Can't open memory stream"); + m_error = LoaderError { String::formatted("Can't open memory stream") }; return; } m_memory_stream = static_cast<InputMemoryStream*>(m_stream.ptr()); - - valid = parse_header(); - if (!valid) - return; } -RefPtr<Buffer> WavLoaderPlugin::get_more_samples(size_t max_bytes_to_read_from_input) +LoaderSamples WavLoaderPlugin::get_more_samples(size_t max_bytes_to_read_from_input) { if (!m_stream) - return nullptr; + return LoaderError { LoaderError::Category::Internal, static_cast<size_t>(m_loaded_samples), "No stream" }; int remaining_samples = m_total_samples - m_loaded_samples; - if (remaining_samples <= 0) { - return nullptr; - } + if (remaining_samples <= 0) + return Buffer::create_empty(); // One "sample" contains data from all channels. // In the Wave spec, this is also called a block. - size_t bytes_per_sample = m_num_channels * pcm_bits_per_sample(m_sample_format) / 8; + size_t bytes_per_sample + = m_num_channels * pcm_bits_per_sample(m_sample_format) / 8; // Might truncate if not evenly divisible by the sample size int max_samples_to_read = static_cast<int>(max_bytes_to_read_from_input) / bytes_per_sample; @@ -71,28 +72,30 @@ RefPtr<Buffer> WavLoaderPlugin::get_more_samples(size_t max_bytes_to_read_from_i auto sample_data_result = ByteBuffer::create_zeroed(bytes_to_read); if (!sample_data_result.has_value()) - return nullptr; + return LoaderError { LoaderError::Category::IO, static_cast<size_t>(m_loaded_samples), "Couldn't allocate sample buffer" }; auto sample_data = sample_data_result.release_value(); m_stream->read_or_error(sample_data.bytes()); - if (m_stream->handle_any_error()) { - return nullptr; - } + if (m_stream->handle_any_error()) + return LoaderError { LoaderError::Category::IO, static_cast<size_t>(m_loaded_samples), "Stream read error" }; - RefPtr<Buffer> buffer = Buffer::from_pcm_data( + auto buffer = Buffer::from_pcm_data( sample_data.bytes(), m_num_channels, m_sample_format); + if (buffer.is_error()) + return LoaderError { LoaderError::Category::Internal, static_cast<size_t>(m_loaded_samples), "Couldn't allocate sample buffer" }; + // m_loaded_samples should contain the amount of actually loaded samples m_loaded_samples += samples_to_read; - return buffer; + return buffer.release_value(); } -void WavLoaderPlugin::seek(const int sample_index) +MaybeLoaderError WavLoaderPlugin::seek(const int sample_index) { dbgln_if(AWAVLOADER_DEBUG, "seek sample_index {}", sample_index); if (sample_index < 0 || sample_index >= m_total_samples) - return; + return LoaderError { LoaderError::Category::Internal, static_cast<size_t>(m_loaded_samples), "Seek outside the sample range" }; size_t sample_offset = m_byte_offset_of_data_samples + (sample_index * m_num_channels * (pcm_bits_per_sample(m_sample_format) / 8)); @@ -104,13 +107,14 @@ void WavLoaderPlugin::seek(const int sample_index) } m_loaded_samples = sample_index; + return {}; } // Specification reference: http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html -bool WavLoaderPlugin::parse_header() +MaybeLoaderError WavLoaderPlugin::parse_header() { if (!m_stream) - return false; + return LoaderError { LoaderError::Category::Internal, 0, "No stream" }; bool ok = true; size_t bytes_read = 0; @@ -142,77 +146,74 @@ bool WavLoaderPlugin::parse_header() return value; }; -#define CHECK_OK(msg) \ - do { \ - if (!ok) { \ - m_error_string = String::formatted("Parsing failed: {}", msg); \ - dbgln_if(AWAVLOADER_DEBUG, m_error_string); \ - return {}; \ - } \ +#define CHECK_OK(category, msg) \ + do { \ + if (!ok) \ + return LoaderError { category, String::formatted("Parsing failed: {}", msg) }; \ } while (0) u32 riff = read_u32(); ok = ok && riff == 0x46464952; // "RIFF" - CHECK_OK("RIFF header"); + CHECK_OK(LoaderError::Category::Format, "RIFF header"); u32 sz = read_u32(); ok = ok && sz < maximum_wav_size; - CHECK_OK("File size"); + CHECK_OK(LoaderError::Category::Format, "File size"); u32 wave = read_u32(); ok = ok && wave == 0x45564157; // "WAVE" - CHECK_OK("WAVE header"); + CHECK_OK(LoaderError::Category::Format, "WAVE header"); u32 fmt_id = read_u32(); ok = ok && fmt_id == 0x20746D66; // "fmt " - CHECK_OK("FMT header"); + CHECK_OK(LoaderError::Category::Format, "FMT header"); u32 fmt_size = read_u32(); ok = ok && (fmt_size == 16 || fmt_size == 18 || fmt_size == 40); - CHECK_OK("FMT size"); + CHECK_OK(LoaderError::Category::Format, "FMT size"); u16 audio_format = read_u16(); - CHECK_OK("Audio format"); // incomplete read check + CHECK_OK(LoaderError::Category::Format, "Audio format"); // incomplete read check ok = ok && (audio_format == WAVE_FORMAT_PCM || audio_format == WAVE_FORMAT_IEEE_FLOAT || audio_format == WAVE_FORMAT_EXTENSIBLE); - CHECK_OK("Audio format PCM/Float"); // value check + CHECK_OK(LoaderError::Category::Unimplemented, "Audio format PCM/Float"); // value check m_num_channels = read_u16(); ok = ok && (m_num_channels == 1 || m_num_channels == 2); - CHECK_OK("Channel count"); + CHECK_OK(LoaderError::Category::Unimplemented, "Channel count"); m_sample_rate = read_u32(); - CHECK_OK("Sample rate"); + CHECK_OK(LoaderError::Category::IO, "Sample rate"); read_u32(); - CHECK_OK("Data rate"); + CHECK_OK(LoaderError::Category::IO, "Data rate"); u16 block_size_bytes = read_u16(); - CHECK_OK("Block size"); + CHECK_OK(LoaderError::Category::IO, "Block size"); u16 bits_per_sample = read_u16(); - CHECK_OK("Bits per sample"); + CHECK_OK(LoaderError::Category::IO, "Bits per sample"); if (audio_format == WAVE_FORMAT_EXTENSIBLE) { ok = ok && (fmt_size == 40); - CHECK_OK("Extensible fmt size"); // value check + CHECK_OK(LoaderError::Category::Format, "Extensible fmt size"); // value check // Discard everything until the GUID. // We've already read 16 bytes from the stream. The GUID starts in another 8 bytes. read_u32(); read_u32(); - CHECK_OK("Discard until GUID"); + CHECK_OK(LoaderError::Category::IO, "Discard until GUID"); // Get the underlying audio format from the first two bytes of GUID u16 guid_subformat = read_u16(); ok = ok && (guid_subformat == WAVE_FORMAT_PCM || guid_subformat == WAVE_FORMAT_IEEE_FLOAT); - CHECK_OK("GUID SubFormat"); + CHECK_OK(LoaderError::Category::Unimplemented, "GUID SubFormat"); audio_format = guid_subformat; } if (audio_format == WAVE_FORMAT_PCM) { ok = ok && (bits_per_sample == 8 || bits_per_sample == 16 || bits_per_sample == 24); - CHECK_OK("Bits per sample (PCM)"); // value check + CHECK_OK(LoaderError::Category::Unimplemented, "Bits per sample (PCM)"); // value check // We only support 8-24 bit audio right now because other formats are uncommon if (bits_per_sample == 8) { @@ -224,7 +225,7 @@ bool WavLoaderPlugin::parse_header() } } else if (audio_format == WAVE_FORMAT_IEEE_FLOAT) { ok = ok && (bits_per_sample == 32 || bits_per_sample == 64); - CHECK_OK("Bits per sample (Float)"); // value check + CHECK_OK(LoaderError::Category::Unimplemented, "Bits per sample (Float)"); // value check // Again, only the common 32 and 64 bit if (bits_per_sample == 32) { @@ -235,7 +236,7 @@ bool WavLoaderPlugin::parse_header() } ok = ok && (block_size_bytes == (m_num_channels * (bits_per_sample / 8))); - CHECK_OK("Block size sanity check"); + CHECK_OK(LoaderError::Category::Format, "Block size sanity check"); dbgln_if(AWAVLOADER_DEBUG, "WAV format {} at {} bit, {} channels, rate {}Hz ", sample_format_name(m_sample_format), pcm_bits_per_sample(m_sample_format), m_num_channels, m_sample_rate); @@ -246,17 +247,17 @@ bool WavLoaderPlugin::parse_header() u8 search_byte = 0; while (true) { search_byte = read_u8(); - CHECK_OK("Reading byte searching for data"); + CHECK_OK(LoaderError::Category::IO, "Reading byte searching for data"); if (search_byte != 0x64) // D continue; search_byte = read_u8(); - CHECK_OK("Reading next byte searching for data"); + CHECK_OK(LoaderError::Category::IO, "Reading next byte searching for data"); if (search_byte != 0x61) // A continue; u16 search_remaining = read_u16(); - CHECK_OK("Reading remaining bytes searching for data"); + CHECK_OK(LoaderError::Category::IO, "Reading remaining bytes searching for data"); if (search_remaining != 0x6174) // TA continue; @@ -266,10 +267,10 @@ bool WavLoaderPlugin::parse_header() } ok = ok && found_data; - CHECK_OK("Found no data chunk"); + CHECK_OK(LoaderError::Category::Format, "Found no data chunk"); ok = ok && data_sz < maximum_wav_size; - CHECK_OK("Data was too large"); + CHECK_OK(LoaderError::Category::Format, "Data was too large"); m_total_samples = data_sz / block_size_bytes; @@ -279,7 +280,6 @@ bool WavLoaderPlugin::parse_header() m_total_samples); m_byte_offset_of_data_samples = bytes_read; - return true; + return {}; } - } diff --git a/Userland/Libraries/LibAudio/WavLoader.h b/Userland/Libraries/LibAudio/WavLoader.h index 2a2976536b..b5842b2ce9 100644 --- a/Userland/Libraries/LibAudio/WavLoader.h +++ b/Userland/Libraries/LibAudio/WavLoader.h @@ -36,20 +36,17 @@ public: explicit WavLoaderPlugin(StringView path); explicit WavLoaderPlugin(const ByteBuffer& buffer); - virtual bool sniff() override { return valid; } - - virtual bool has_error() override { return !m_error_string.is_null(); } - virtual const String& error_string() override { return m_error_string; } + virtual MaybeLoaderError initialize() override; // The Buffer returned contains input data resampled at the // destination audio device sample rate. - virtual RefPtr<Buffer> get_more_samples(size_t max_bytes_to_read_from_input = 128 * KiB) override; + virtual LoaderSamples get_more_samples(size_t max_bytes_to_read_from_input = 128 * KiB) override; - virtual void reset() override { return seek(0); } + virtual MaybeLoaderError reset() override { return seek(0); } // sample_index 0 is the start of the raw audio sample data // within the file/stream. - virtual void seek(const int sample_index) override; + virtual MaybeLoaderError seek(const int sample_index) override; virtual int loaded_samples() override { return m_loaded_samples; } virtual int total_samples() override { return m_total_samples; } @@ -59,13 +56,12 @@ public: virtual RefPtr<Core::File> file() override { return m_file; } private: - bool parse_header(); + MaybeLoaderError parse_header(); - bool valid { false }; RefPtr<Core::File> m_file; OwnPtr<AK::InputStream> m_stream; AK::InputMemoryStream* m_memory_stream; - String m_error_string; + Optional<LoaderError> m_error {}; u32 m_sample_rate { 0 }; u16 m_num_channels { 0 }; diff --git a/Userland/Services/AudioServer/ClientConnection.cpp b/Userland/Services/AudioServer/ClientConnection.cpp index ee84a59536..919db82270 100644 --- a/Userland/Services/AudioServer/ClientConnection.cpp +++ b/Userland/Services/AudioServer/ClientConnection.cpp @@ -97,7 +97,8 @@ Messages::AudioServer::EnqueueBufferResponse ClientConnection::enqueue_buffer(Co if (m_queue->is_full()) return false; - m_queue->enqueue(Audio::Buffer::create_with_anonymous_buffer(buffer, buffer_id, sample_count)); + // There's not a big allocation to worry about here. + m_queue->enqueue(MUST(Audio::Buffer::create_with_anonymous_buffer(buffer, buffer_id, sample_count))); return true; } diff --git a/Userland/Utilities/aplay.cpp b/Userland/Utilities/aplay.cpp index e7b4f2d1d0..47fdcbd8e8 100644 --- a/Userland/Utilities/aplay.cpp +++ b/Userland/Utilities/aplay.cpp @@ -4,6 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include <AK/Types.h> #include <LibAudio/ClientConnection.h> #include <LibAudio/Loader.h> #include <LibCore/ArgsParser.h> @@ -11,6 +12,10 @@ #include <LibMain/Main.h> #include <stdio.h> +// The Kernel has issues with very large anonymous buffers. +// FIXME: This appears to be fine for now, but it's really a hack. +constexpr size_t LOAD_CHUNK_SIZE = 128 * KiB; + ErrorOr<int> serenity_main(Main::Arguments arguments) { const char* path = nullptr; @@ -24,11 +29,12 @@ ErrorOr<int> serenity_main(Main::Arguments arguments) Core::EventLoop loop; auto audio_client = Audio::ClientConnection::construct(); - NonnullRefPtr<Audio::Loader> loader = Audio::Loader::create(path); - if (loader->has_error()) { - warnln("Failed to load audio file: {}", loader->error_string()); + auto maybe_loader = Audio::Loader::create(path); + if (maybe_loader.is_error()) { + warnln("Failed to load audio file: {}", maybe_loader.error().description); return 1; } + auto loader = maybe_loader.release_value(); outln("\033[34;1m Playing\033[0m: {}", path); outln("\033[34;1m Format\033[0m: {} Hz, {}-bit, {}", @@ -39,25 +45,43 @@ ErrorOr<int> serenity_main(Main::Arguments arguments) auto resampler = Audio::ResampleHelper<double>(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<size_t>(LOAD_CHUNK_SIZE * static_cast<double>(loader->sample_rate()) / static_cast<double>(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; + for (;;) { - auto samples = loader->get_more_samples(); - if (samples) { - out("\033[u"); - out("{}/{}", loader->loaded_samples(), loader->total_samples()); - fflush(stdout); - resampler.reset(); - samples = Audio::resample_buffer(resampler, *samples); - audio_client->enqueue(*samples); - } else if (loader->has_error()) { - outln(); - outln("Error: {}", loader->error_string()); - break; - } else if (should_loop) { - loader->reset(); - } else if (audio_client->get_remaining_samples()) { - sleep(1); + auto samples = loader->get_more_samples(load_size); + if (!samples.is_error()) { + if (samples.value()->sample_count() > 0) { + // We can read and enqueue more samples + out("\033[u"); + out("{}/{}", loader->loaded_samples(), loader->total_samples()); + fflush(stdout); + resampler.reset(); + auto resampled_samples = TRY(Audio::resample_buffer(resampler, *samples.value())); + audio_client->async_enqueue(*resampled_samples); + } else if (should_loop) { + // We're done: now loop + auto result = loader->reset(); + if (result.is_error()) { + 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) { + // We're done and the server is done + break; + } + while (audio_client->get_remaining_samples() > min_buffer_size) { + // The server has enough data for now + sleep(1); + } } else { - break; + outln(); + outln("Error: {} (at {:x})", samples.error().description, samples.error().index); + return 1; } } outln(); diff --git a/Userland/Utilities/asctl.cpp b/Userland/Utilities/asctl.cpp index 19780c775c..4144f0e310 100644 --- a/Userland/Utilities/asctl.cpp +++ b/Userland/Utilities/asctl.cpp @@ -13,6 +13,7 @@ #include <LibCore/ArgsParser.h> #include <LibCore/EventLoop.h> #include <LibCore/File.h> +#include <LibCore/System.h> #include <LibMain/Main.h> #include <math.h> #include <stdio.h> @@ -41,6 +42,9 @@ ErrorOr<int> serenity_main(Main::Arguments arguments) args_parser.add_positional_argument(command_arguments, "Arguments for the command", "args", Core::ArgsParser::Required::No); args_parser.parse(arguments); + TRY(Core::System::unveil(nullptr, nullptr)); + TRY(Core::System::pledge("stdio rpath wpath recvfd", nullptr)); + if (command.equals_ignoring_case("get") || command == "g") { // Get variables Vector<AudioVariable> values_to_print; |