Coverage Report

Created: 2025-11-16 07:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/serenity/Userland/Libraries/LibAudio/Loader.cpp
Line
Count
Source
1
/*
2
 * Copyright (c) 2018-2023, the SerenityOS developers.
3
 *
4
 * SPDX-License-Identifier: BSD-2-Clause
5
 */
6
7
#include <AK/TypedTransfer.h>
8
#include <LibAudio/FlacLoader.h>
9
#include <LibAudio/Loader.h>
10
#include <LibAudio/MP3Loader.h>
11
#include <LibAudio/QOALoader.h>
12
#include <LibAudio/WavLoader.h>
13
#include <LibCore/MappedFile.h>
14
15
namespace Audio {
16
17
LoaderPlugin::LoaderPlugin(NonnullOwnPtr<SeekableStream> stream)
18
3.50k
    : m_stream(move(stream))
19
3.50k
{
20
3.50k
}
21
22
Loader::Loader(NonnullOwnPtr<LoaderPlugin> plugin)
23
0
    : m_plugin(move(plugin))
24
0
{
25
0
}
26
27
struct LoaderPluginInitializer {
28
    bool (*sniff)(SeekableStream&);
29
    ErrorOr<NonnullOwnPtr<LoaderPlugin>, LoaderError> (*create)(NonnullOwnPtr<SeekableStream>);
30
};
31
32
#define ENUMERATE_LOADER_PLUGINS    \
33
    __ENUMERATE_LOADER_PLUGIN(Wav)  \
34
    __ENUMERATE_LOADER_PLUGIN(Flac) \
35
    __ENUMERATE_LOADER_PLUGIN(QOA)  \
36
    __ENUMERATE_LOADER_PLUGIN(MP3)
37
38
static constexpr LoaderPluginInitializer s_initializers[] = {
39
#define __ENUMERATE_LOADER_PLUGIN(Type) \
40
    { Type##LoaderPlugin::sniff, Type##LoaderPlugin::create },
41
    ENUMERATE_LOADER_PLUGINS
42
#undef __ENUMERATE_LOADER_PLUGIN
43
};
44
45
ErrorOr<NonnullRefPtr<Loader>, LoaderError> Loader::create(StringView path)
46
0
{
47
0
    auto stream = TRY(Core::MappedFile::map(path, Core::MappedFile::Mode::ReadOnly));
48
0
    auto plugin = TRY(Loader::create_plugin(move(stream)));
49
0
    return adopt_ref(*new (nothrow) Loader(move(plugin)));
50
0
}
51
52
ErrorOr<NonnullRefPtr<Loader>, LoaderError> Loader::create(ReadonlyBytes buffer)
53
0
{
54
0
    auto stream = TRY(try_make<FixedMemoryStream>(buffer));
55
0
    auto plugin = TRY(Loader::create_plugin(move(stream)));
56
0
    return adopt_ref(*new (nothrow) Loader(move(plugin)));
57
0
}
58
59
ErrorOr<NonnullOwnPtr<LoaderPlugin>, LoaderError> Loader::create_plugin(NonnullOwnPtr<SeekableStream> stream)
60
0
{
61
0
    for (auto const& loader : s_initializers) {
62
0
        if (loader.sniff(*stream)) {
63
0
            TRY(stream->seek(0, SeekMode::SetPosition));
64
0
            return loader.create(move(stream));
65
0
        }
66
0
        TRY(stream->seek(0, SeekMode::SetPosition));
67
0
    }
68
69
0
    return LoaderError { "No loader plugin available"_fly_string };
70
0
}
71
72
LoaderSamples Loader::get_more_samples(size_t samples_to_read_from_input)
73
0
{
74
0
    if (m_plugin_at_end_of_stream && m_buffer.is_empty())
75
0
        return FixedArray<Sample> {};
76
77
0
    size_t remaining_samples = total_samples() - loaded_samples();
78
0
    size_t samples_to_read = min(remaining_samples, samples_to_read_from_input);
79
0
    auto samples = TRY(FixedArray<Sample>::create(samples_to_read));
80
81
0
    size_t sample_index = 0;
82
83
0
    if (m_buffer.size() > 0) {
84
0
        size_t to_transfer = min(m_buffer.size(), samples_to_read);
85
0
        AK::TypedTransfer<Sample>::move(samples.data(), m_buffer.data(), to_transfer);
86
0
        if (to_transfer < m_buffer.size())
87
0
            m_buffer.remove(0, to_transfer);
88
0
        else
89
0
            m_buffer.clear_with_capacity();
90
91
0
        sample_index += to_transfer;
92
0
    }
93
94
0
    while (sample_index < samples_to_read) {
95
0
        auto chunk_data = TRY(m_plugin->load_chunks(samples_to_read - sample_index));
96
0
        chunk_data.remove_all_matching([](auto& chunk) { return chunk.is_empty(); });
97
0
        if (chunk_data.is_empty()) {
98
0
            m_plugin_at_end_of_stream = true;
99
0
            break;
100
0
        }
101
0
        for (auto& chunk : chunk_data) {
102
0
            if (sample_index < samples_to_read) {
103
0
                auto count = min(samples_to_read - sample_index, chunk.size());
104
0
                AK::TypedTransfer<Sample>::move(samples.span().offset(sample_index), chunk.data(), count);
105
                // We didn't read all of the chunk; transfer the rest into the buffer.
106
0
                if (count < chunk.size()) {
107
0
                    auto remaining_samples_count = chunk.size() - count;
108
                    // We will always have an empty buffer at this point!
109
0
                    TRY(m_buffer.try_append(chunk.span().offset(count), remaining_samples_count));
110
0
                }
111
0
            } else {
112
                // We're now past what the user requested. Transfer the entirety of the data into the buffer.
113
0
                TRY(m_buffer.try_append(chunk.data(), chunk.size()));
114
0
            }
115
0
            sample_index += chunk.size();
116
0
        }
117
0
    }
118
119
0
    return samples;
120
0
}
121
122
}