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(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(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 music_samples_to_buffer(Array::max(), sample.right / (double)NumericLimits::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 = 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 #include #include #include 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 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& 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::max(); } -NonnullRefPtr Buffer::from_pcm_data(ReadonlyBytes data, int num_channels, PcmSampleFormat sample_format) +ErrorOr> 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::from_pcm_stream(InputMemoryStream& stream, int num_channels, PcmSampleFormat sample_format, int num_samples) +ErrorOr> Buffer::from_pcm_stream(InputMemoryStream& stream, int num_channels, PcmSampleFormat sample_format, int num_samples) { Vector fdata; fdata.ensure_capacity(num_samples); @@ -189,7 +189,7 @@ Vector ResampleHelper::resample(Vector to_re template Vector ResampleHelper::resample(Vector); template Vector ResampleHelper::resample(Vector); -NonnullRefPtr resample_buffer(ResampleHelper& resampler, Buffer const& to_resample) +ErrorOr> resample_buffer(ResampleHelper& resampler, Buffer const& to_resample) { Vector 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 +#include #include +#include +#include #include #include #include +#include #include #include #include @@ -67,19 +71,20 @@ private: // A buffer of audio samples. class Buffer : public RefCounted { public: - static NonnullRefPtr from_pcm_data(ReadonlyBytes data, int num_channels, PcmSampleFormat sample_format); - static NonnullRefPtr from_pcm_stream(InputMemoryStream& stream, int num_channels, PcmSampleFormat sample_format, int num_samples); - static NonnullRefPtr create_with_samples(Vector&& samples) + static ErrorOr> from_pcm_data(ReadonlyBytes data, int num_channels, PcmSampleFormat sample_format); + static ErrorOr> from_pcm_stream(InputMemoryStream& stream, int num_channels, PcmSampleFormat sample_format, int num_samples); + static ErrorOr> create_with_samples(Vector&& samples) { - return adopt_ref(*new Buffer(move(samples))); + return adopt_nonnull_ref_or_enomem(new (nothrow) Buffer(move(samples))); } - static NonnullRefPtr create_with_anonymous_buffer(Core::AnonymousBuffer buffer, i32 buffer_id, int sample_count) + static ErrorOr> 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 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 resample_buffer(ResampleHelper& resampler, Buffer const& to_resample); +ErrorOr> resample_buffer(ResampleHelper& 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 #include #include #include #include #include -#include #include #include +#include +#include +#include +#include +#include +#include #include -#include 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(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(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>()); } return InputBitStream(m_stream->get()); }(); - ScopeGuard clear_bit_input_errors([&bit_input] { bit_input.handle_any_error(); }); + ScopeGuard handle_all_errors([&bit_input, this] { + m_stream->handle_any_error(); + 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 {}; \ - } \ + // A mixture of VERIFY and the non-crashing TRY(). +#define FLAC_VERIFY(check, category, msg) \ + do { \ + if (!(check)) { \ + return LoaderError { category, static_cast(m_data_start_location), String::formatted("FLAC header: {}", msg) }; \ + } \ } while (0) // Magic number u32 flac = static_cast(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(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(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(streaminfo_data.read_bits_big_endian(24)); m_max_frame_size = static_cast(streaminfo_data.read_bits_big_endian(24)); m_sample_rate = static_cast(streaminfo_data.read_bits_big_endian(20)); - ok = ok && (m_sample_rate <= 655350); - CHECK_OK("Sample rate"); - m_num_channels = static_cast(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(streaminfo_data.read_bits_big_endian(3)) + 1; // 0 = one channel u8 bits_per_sample = static_cast(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(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 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(m_total_samples) / static_cast(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(m_total_samples) / static_cast(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 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(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 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 samples; ssize_t remaining_samples = static_cast(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 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; +#define FLAC_VERIFY(check, category, msg) \ + do { \ + if (!(check)) { \ + return LoaderError { category, static_cast(m_current_sample_or_frame), String::formatted("FLAC header: {}", msg) }; \ + } \ + } while (0) 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); \ - } \ - } while (0) // 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(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(bit_stream.read_bits_big_endian(4))); - CHECK_ERROR_STRING; + u32 sample_count = TRY(convert_sample_count_code(static_cast(bit_stream.read_bits_big_endian(4)))); - u32 frame_sample_rate = convert_sample_rate_code(static_cast(bit_stream.read_bits_big_endian(4))); - CHECK_ERROR_STRING; + u32 frame_sample_rate = TRY(convert_sample_rate_code(static_cast(bit_stream.read_bits_big_endian(4)))); u8 channel_type_num = static_cast(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(bit_stream.read_bits_big_endian(3))); - CHECK_ERROR_STRING; + PcmSampleFormat bit_depth = TRY(convert_bit_depth_code(static_cast(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 subframe_samples = parse_subframe(new_subframe, bit_stream); - CHECK_ERROR_STRING; + FlacSubframeHeader new_subframe = TRY(next_subframe_header(bit_stream, i)); + Vector subframe_samples = TRY(parse_subframe(new_subframe, bit_stream)); current_subframes.append(move(subframe_samples)); } @@ -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 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(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 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(m_current_sample_or_frame), "Invalid sample rate code" }; } } -PcmSampleFormat FlacLoaderPlugin::convert_bit_depth_code(u8 bit_depth_code) +ErrorOr 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(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(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 FlacLoaderPlugin::next_subframe_header(InputBitStream& bit_stream, u8 channel_index) { u8 bits_per_sample = static_cast(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(m_current_sample_or_frame), "Zero bit padding" }; // subframe type (encoding) u8 subframe_code = static_cast(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(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 FlacLoaderPlugin::parse_subframe(FlacSubframeHeader& subframe_header, InputBitStream& bit_input) +ErrorOr, LoaderError> FlacLoaderPlugin::parse_subframe(FlacSubframeHeader& subframe_header, InputBitStream& bit_input) { Vector samples; @@ -605,25 +528,21 @@ Vector 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(m_current_sample_or_frame), "Unhandled FLAC subframe type" }; } for (size_t i = 0; i < samples.size(); ++i) { @@ -635,7 +554,7 @@ Vector FlacLoaderPlugin::parse_subframe(FlacSubframeHeader& subframe_header } // Decode a subframe that isn't actually encoded, usually seen in random data -Vector FlacLoaderPlugin::decode_verbatim(FlacSubframeHeader& subframe, InputBitStream& bit_input) +ErrorOr, LoaderError> FlacLoaderPlugin::decode_verbatim(FlacSubframeHeader& subframe, InputBitStream& bit_input) { Vector decoded; decoded.ensure_capacity(m_current_frame->sample_count); @@ -651,7 +570,7 @@ Vector 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 FlacLoaderPlugin::decode_custom_lpc(FlacSubframeHeader& subframe, InputBitStream& bit_input) +ErrorOr, LoaderError> FlacLoaderPlugin::decode_custom_lpc(FlacSubframeHeader& subframe, InputBitStream& bit_input) { Vector decoded; decoded.ensure_capacity(m_current_frame->sample_count); @@ -666,10 +585,8 @@ Vector FlacLoaderPlugin::decode_custom_lpc(FlacSubframeHeader& subframe, In // precision of the coefficients u8 lpc_precision = static_cast(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(m_current_sample_or_frame), "Invalid linear predictor coefficient precision" }; lpc_precision += 1; // shift needed on the data (signed!) @@ -687,7 +604,7 @@ Vector 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 FlacLoaderPlugin::decode_custom_lpc(FlacSubframeHeader& subframe, In } // Decode a subframe encoded with one of the fixed linear predictor codings -Vector FlacLoaderPlugin::decode_fixed_lpc(FlacSubframeHeader& subframe, InputBitStream& bit_input) +ErrorOr, LoaderError> FlacLoaderPlugin::decode_fixed_lpc(FlacSubframeHeader& subframe, InputBitStream& bit_input) { Vector decoded; decoded.ensure_capacity(m_current_frame->sample_count); @@ -720,9 +637,8 @@ Vector 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 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(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 FlacLoaderPlugin::decode_residual(Vector& decoded, FlacSubframeHeader& subframe, InputBitStream& bit_input) +ErrorOr, LoaderError> FlacLoaderPlugin::decode_residual(Vector& decoded, FlacSubframeHeader& subframe, InputBitStream& bit_input) { u8 residual_mode = static_cast(bit_input.read_bits_big_endian(2)); u8 partition_order = static_cast(bit_input.read_bits_big_endian(4)); @@ -777,10 +692,8 @@ Vector FlacLoaderPlugin::decode_residual(Vector& 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(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 #include +#include #include #include #include @@ -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 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(m_loaded_samples); } virtual int total_samples() override { return static_cast(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 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 next_subframe_header(InputBitStream& bit_input, u8 channel_index); // Helper of next_frame that decompresses a subframe - Vector parse_subframe(FlacSubframeHeader& subframe_header, InputBitStream& bit_input); + ErrorOr, LoaderError> parse_subframe(FlacSubframeHeader& subframe_header, InputBitStream& bit_input); // Subframe-internal data decoders (heavy lifting) - Vector decode_fixed_lpc(FlacSubframeHeader& subframe, InputBitStream& bit_input); - Vector decode_verbatim(FlacSubframeHeader& subframe, InputBitStream& bit_input); - Vector decode_custom_lpc(FlacSubframeHeader& subframe, InputBitStream& bit_input); - Vector decode_residual(Vector& decoded, FlacSubframeHeader& subframe, InputBitStream& bit_input); + ErrorOr, LoaderError> decode_fixed_lpc(FlacSubframeHeader& subframe, InputBitStream& bit_input); + ErrorOr, LoaderError> decode_verbatim(FlacSubframeHeader& subframe, InputBitStream& bit_input); + ErrorOr, LoaderError> decode_custom_lpc(FlacSubframeHeader& subframe, InputBitStream& bit_input); + ErrorOr, LoaderError> decode_residual(Vector& decoded, FlacSubframeHeader& subframe, InputBitStream& bit_input); // decode a single rice partition that has its own rice parameter ALWAYS_INLINE Vector 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 convert_sample_count_code(u8 sample_count_code); + ALWAYS_INLINE ErrorOr convert_sample_rate_code(u8 sample_rate_code); + ALWAYS_INLINE ErrorOr convert_bit_depth_code(u8 bit_depth_code); - bool m_valid { false }; RefPtr m_file; - String m_error_string; + Optional 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 +#include #include namespace Audio { -Loader::Loader(StringView path) +Loader::Loader(NonnullOwnPtr plugin) + : m_plugin(move(plugin)) { - m_plugin = make(path); - if (m_plugin->sniff()) - return; - m_plugin = make(path); - if (m_plugin->sniff()) - return; - m_plugin = nullptr; } -Loader::Loader(const ByteBuffer& buffer) +Result, LoaderError> Loader::try_create(StringView path) { - m_plugin = make(buffer); - if (m_plugin->sniff()) - return; - m_plugin = make(buffer); - if (m_plugin->sniff()) { - dbgln("FLAC sniff successful"); - return; - } - m_plugin = nullptr; + NonnullOwnPtr 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, LoaderError> Loader::try_create(ByteBuffer const& buffer) +{ + NonnullOwnPtr 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 +#include +#include #include #include +#include #include +#include #include +#include #include 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, LoaderError>; +using MaybeLoaderError = Result; + class LoaderPlugin { public: virtual ~LoaderPlugin() { } - virtual bool sniff() = 0; + virtual MaybeLoaderError initialize() = 0; - virtual bool has_error() { return false; } - virtual const String& error_string() { return empty_string; } + virtual LoaderSamples get_more_samples(size_t max_bytes_to_read_from_input = 128 * KiB) = 0; - virtual RefPtr get_more_samples(size_t max_bytes_to_read_from_input = 128 * KiB) = 0; + virtual MaybeLoaderError reset() = 0; - virtual void 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 { public: - static NonnullRefPtr create(StringView path) { return adopt_ref(*new Loader(path)); } - static NonnullRefPtr create(const ByteBuffer& buffer) { return adopt_ref(*new Loader(buffer)); } + static Result, LoaderError> create(StringView path) { return adopt_ref(*new Loader(TRY(try_create(path)))); } + static Result, LoaderError> create(ByteBuffer const& buffer) { return adopt_ref(*new Loader(TRY(try_create(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; } + 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); } - RefPtr 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; } + MaybeLoaderError reset() const { return m_plugin->reset(); } + MaybeLoaderError seek(const int position) const { return m_plugin->seek(position); } - 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 file() const { return m_plugin ? m_plugin->file() : nullptr; } + 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 file() const { return m_plugin->file(); } private: - explicit Loader(StringView path); - explicit Loader(const ByteBuffer& buffer); + static Result, LoaderError> try_create(StringView path); + static Result, LoaderError> try_create(ByteBuffer const& buffer); - mutable OwnPtr m_plugin; + explicit Loader(NonnullOwnPtr); + + mutable NonnullOwnPtr 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 . + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +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 #include #include +#include #include #include @@ -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(*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(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(m_stream.ptr()); - - valid = parse_header(); - if (!valid) - return; } -RefPtr 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(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(max_bytes_to_read_from_input) / bytes_per_sample; @@ -71,28 +72,30 @@ RefPtr 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(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(m_loaded_samples), "Stream read error" }; - RefPtr 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(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(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 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 file() override { return m_file; } private: - bool parse_header(); + MaybeLoaderError parse_header(); - bool valid { false }; RefPtr m_file; OwnPtr m_stream; AK::InputMemoryStream* m_memory_stream; - String m_error_string; + Optional 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 #include #include #include @@ -11,6 +12,10 @@ #include #include +// 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 serenity_main(Main::Arguments arguments) { const char* path = nullptr; @@ -24,11 +29,12 @@ ErrorOr serenity_main(Main::Arguments arguments) Core::EventLoop loop; auto audio_client = Audio::ClientConnection::construct(); - NonnullRefPtr 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 serenity_main(Main::Arguments arguments) auto resampler = Audio::ResampleHelper(loader->sample_rate(), audio_client->get_sample_rate()); + // If we're downsampling, we need to appropriately load more samples at once. + size_t const load_size = static_cast(LOAD_CHUNK_SIZE * static_cast(loader->sample_rate()) / static_cast(audio_client->get_sample_rate())); + // We assume that the loader can load samples at at least 2x speed (testing confirms 9x-12x for FLAC, 14x for WAV). + // Therefore, when the server-side buffer can only play as long as the time it takes us to load a chunk, + // we give it new data. + int const min_buffer_size = load_size / 2; + 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 #include #include +#include #include #include #include @@ -41,6 +42,9 @@ ErrorOr 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 values_to_print;