mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 21:07:34 +00:00
Libraries: Move to Userland/Libraries/
This commit is contained in:
parent
dc28c07fa5
commit
13d7c09125
1857 changed files with 266 additions and 274 deletions
134
Userland/Libraries/LibAudio/Buffer.cpp
Normal file
134
Userland/Libraries/LibAudio/Buffer.cpp
Normal file
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <LibAudio/Buffer.h>
|
||||
|
||||
namespace Audio {
|
||||
|
||||
template<typename SampleReader>
|
||||
static void read_samples_from_stream(InputMemoryStream& stream, SampleReader read_sample, Vector<Sample>& samples, ResampleHelper& resampler, int num_channels)
|
||||
{
|
||||
double norm_l = 0;
|
||||
double norm_r = 0;
|
||||
|
||||
switch (num_channels) {
|
||||
case 1:
|
||||
for (;;) {
|
||||
while (resampler.read_sample(norm_l, norm_r)) {
|
||||
samples.append(Sample(norm_l));
|
||||
}
|
||||
norm_l = read_sample(stream);
|
||||
|
||||
if (stream.handle_any_error()) {
|
||||
break;
|
||||
}
|
||||
resampler.process_sample(norm_l, norm_r);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
for (;;) {
|
||||
while (resampler.read_sample(norm_l, norm_r)) {
|
||||
samples.append(Sample(norm_l, norm_r));
|
||||
}
|
||||
norm_l = read_sample(stream);
|
||||
norm_r = read_sample(stream);
|
||||
|
||||
if (stream.handle_any_error()) {
|
||||
break;
|
||||
}
|
||||
resampler.process_sample(norm_l, norm_r);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
static double read_norm_sample_24(InputMemoryStream& stream)
|
||||
{
|
||||
u8 byte = 0;
|
||||
stream >> byte;
|
||||
u32 sample1 = byte;
|
||||
stream >> byte;
|
||||
u32 sample2 = byte;
|
||||
stream >> byte;
|
||||
u32 sample3 = byte;
|
||||
|
||||
i32 value = 0;
|
||||
value = sample1 << 8;
|
||||
value |= (sample2 << 16);
|
||||
value |= (sample3 << 24);
|
||||
return double(value) / NumericLimits<i32>::max();
|
||||
}
|
||||
|
||||
static double read_norm_sample_16(InputMemoryStream& stream)
|
||||
{
|
||||
LittleEndian<i16> sample;
|
||||
stream >> sample;
|
||||
return double(sample) / NumericLimits<i16>::max();
|
||||
}
|
||||
|
||||
static double read_norm_sample_8(InputMemoryStream& stream)
|
||||
{
|
||||
u8 sample = 0;
|
||||
stream >> sample;
|
||||
return double(sample) / NumericLimits<u8>::max();
|
||||
}
|
||||
|
||||
RefPtr<Buffer> Buffer::from_pcm_data(ReadonlyBytes data, ResampleHelper& resampler, int num_channels, int bits_per_sample)
|
||||
{
|
||||
InputMemoryStream stream { data };
|
||||
return from_pcm_stream(stream, resampler, num_channels, bits_per_sample, data.size() / (bits_per_sample / 8));
|
||||
}
|
||||
|
||||
RefPtr<Buffer> Buffer::from_pcm_stream(InputMemoryStream& stream, ResampleHelper& resampler, int num_channels, int bits_per_sample, int num_samples)
|
||||
{
|
||||
Vector<Sample> fdata;
|
||||
fdata.ensure_capacity(num_samples);
|
||||
|
||||
switch (bits_per_sample) {
|
||||
case 8:
|
||||
read_samples_from_stream(stream, read_norm_sample_8, fdata, resampler, num_channels);
|
||||
break;
|
||||
case 16:
|
||||
read_samples_from_stream(stream, read_norm_sample_16, fdata, resampler, num_channels);
|
||||
break;
|
||||
case 24:
|
||||
read_samples_from_stream(stream, read_norm_sample_24, fdata, resampler, num_channels);
|
||||
break;
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
// We should handle this in a better way above, but for now --
|
||||
// just make sure we're good. Worst case we just write some 0s where they
|
||||
// don't belong.
|
||||
ASSERT(!stream.handle_any_error());
|
||||
|
||||
return Buffer::create_with_samples(move(fdata));
|
||||
}
|
||||
|
||||
}
|
148
Userland/Libraries/LibAudio/Buffer.h
Normal file
148
Userland/Libraries/LibAudio/Buffer.h
Normal file
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/ByteBuffer.h>
|
||||
#include <AK/MemoryStream.h>
|
||||
#include <AK/SharedBuffer.h>
|
||||
#include <AK/Types.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace Audio {
|
||||
|
||||
// A single sample in an audio buffer.
|
||||
// Values are floating point, and should range from -1.0 to +1.0
|
||||
struct Sample {
|
||||
Sample()
|
||||
: left(0)
|
||||
, right(0)
|
||||
{
|
||||
}
|
||||
|
||||
// For mono
|
||||
Sample(double left)
|
||||
: left(left)
|
||||
, right(left)
|
||||
{
|
||||
}
|
||||
|
||||
// For stereo
|
||||
Sample(double left, double right)
|
||||
: left(left)
|
||||
, right(right)
|
||||
{
|
||||
}
|
||||
|
||||
void clip()
|
||||
{
|
||||
if (left > 1)
|
||||
left = 1;
|
||||
else if (left < -1)
|
||||
left = -1;
|
||||
|
||||
if (right > 1)
|
||||
right = 1;
|
||||
else if (right < -1)
|
||||
right = -1;
|
||||
}
|
||||
|
||||
void scale(int percent)
|
||||
{
|
||||
double pct = (double)percent / 100.0;
|
||||
left *= pct;
|
||||
right *= pct;
|
||||
}
|
||||
|
||||
Sample& operator+=(const Sample& other)
|
||||
{
|
||||
left += other.left;
|
||||
right += other.right;
|
||||
return *this;
|
||||
}
|
||||
|
||||
double left;
|
||||
double right;
|
||||
};
|
||||
|
||||
// Small helper to resample from one playback rate to another
|
||||
// This isn't really "smart", in that we just insert (or drop) samples.
|
||||
// Should do better...
|
||||
class ResampleHelper {
|
||||
public:
|
||||
ResampleHelper(double source, double target);
|
||||
|
||||
void process_sample(double sample_l, double sample_r);
|
||||
bool read_sample(double& next_l, double& next_r);
|
||||
|
||||
private:
|
||||
const double m_ratio;
|
||||
double m_current_ratio { 0 };
|
||||
double m_last_sample_l { 0 };
|
||||
double m_last_sample_r { 0 };
|
||||
};
|
||||
|
||||
// A buffer of audio samples, normalized to 44100hz.
|
||||
class Buffer : public RefCounted<Buffer> {
|
||||
public:
|
||||
static RefPtr<Buffer> from_pcm_data(ReadonlyBytes data, ResampleHelper& resampler, int num_channels, int bits_per_sample);
|
||||
static RefPtr<Buffer> from_pcm_stream(InputMemoryStream& stream, ResampleHelper& resampler, int num_channels, int bits_per_sample, int num_samples);
|
||||
static NonnullRefPtr<Buffer> create_with_samples(Vector<Sample>&& samples)
|
||||
{
|
||||
return adopt(*new Buffer(move(samples)));
|
||||
}
|
||||
static NonnullRefPtr<Buffer> create_with_shared_buffer(NonnullRefPtr<SharedBuffer>&& buffer, int sample_count)
|
||||
{
|
||||
return adopt(*new Buffer(move(buffer), sample_count));
|
||||
}
|
||||
|
||||
const Sample* samples() const { return (const Sample*)data(); }
|
||||
int sample_count() const { return m_sample_count; }
|
||||
const void* data() const { return m_buffer->data<void>(); }
|
||||
int size_in_bytes() const { return m_sample_count * (int)sizeof(Sample); }
|
||||
int shbuf_id() const { return m_buffer->shbuf_id(); }
|
||||
SharedBuffer& shared_buffer() { return *m_buffer; }
|
||||
|
||||
private:
|
||||
explicit Buffer(Vector<Sample>&& samples)
|
||||
: m_buffer(*SharedBuffer::create_with_size(samples.size() * sizeof(Sample)))
|
||||
, m_sample_count(samples.size())
|
||||
{
|
||||
memcpy(m_buffer->data<void>(), samples.data(), samples.size() * sizeof(Sample));
|
||||
}
|
||||
|
||||
explicit Buffer(NonnullRefPtr<SharedBuffer>&& buffer, int sample_count)
|
||||
: m_buffer(move(buffer))
|
||||
, m_sample_count(sample_count)
|
||||
{
|
||||
}
|
||||
|
||||
NonnullRefPtr<SharedBuffer> m_buffer;
|
||||
const int m_sample_count;
|
||||
};
|
||||
|
||||
}
|
15
Userland/Libraries/LibAudio/CMakeLists.txt
Normal file
15
Userland/Libraries/LibAudio/CMakeLists.txt
Normal file
|
@ -0,0 +1,15 @@
|
|||
set(SOURCES
|
||||
Buffer.cpp
|
||||
ClientConnection.cpp
|
||||
Loader.cpp
|
||||
WavLoader.cpp
|
||||
WavWriter.cpp
|
||||
)
|
||||
|
||||
set(GENERATED_SOURCES
|
||||
../../Services/AudioServer/AudioClientEndpoint.h
|
||||
../../Services/AudioServer/AudioServerEndpoint.h
|
||||
)
|
||||
|
||||
serenity_lib(LibAudio audio)
|
||||
target_link_libraries(LibAudio LibCore LibIPC)
|
125
Userland/Libraries/LibAudio/ClientConnection.cpp
Normal file
125
Userland/Libraries/LibAudio/ClientConnection.cpp
Normal file
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <AK/SharedBuffer.h>
|
||||
#include <LibAudio/Buffer.h>
|
||||
#include <LibAudio/ClientConnection.h>
|
||||
|
||||
namespace Audio {
|
||||
|
||||
ClientConnection::ClientConnection()
|
||||
: IPC::ServerConnection<AudioClientEndpoint, AudioServerEndpoint>(*this, "/tmp/portal/audio")
|
||||
{
|
||||
}
|
||||
|
||||
void ClientConnection::handshake()
|
||||
{
|
||||
auto response = send_sync<Messages::AudioServer::Greet>();
|
||||
set_my_client_id(response->client_id());
|
||||
}
|
||||
|
||||
void ClientConnection::enqueue(const Buffer& buffer)
|
||||
{
|
||||
for (;;) {
|
||||
const_cast<Buffer&>(buffer).shared_buffer().share_with(server_pid());
|
||||
auto response = send_sync<Messages::AudioServer::EnqueueBuffer>(buffer.shbuf_id(), buffer.sample_count());
|
||||
if (response->success())
|
||||
break;
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
bool ClientConnection::try_enqueue(const Buffer& buffer)
|
||||
{
|
||||
const_cast<Buffer&>(buffer).shared_buffer().share_with(server_pid());
|
||||
auto response = send_sync<Messages::AudioServer::EnqueueBuffer>(buffer.shbuf_id(), buffer.sample_count());
|
||||
return response->success();
|
||||
}
|
||||
|
||||
bool ClientConnection::get_muted()
|
||||
{
|
||||
return send_sync<Messages::AudioServer::GetMuted>()->muted();
|
||||
}
|
||||
|
||||
void ClientConnection::set_muted(bool muted)
|
||||
{
|
||||
send_sync<Messages::AudioServer::SetMuted>(muted);
|
||||
}
|
||||
|
||||
int ClientConnection::get_main_mix_volume()
|
||||
{
|
||||
return send_sync<Messages::AudioServer::GetMainMixVolume>()->volume();
|
||||
}
|
||||
|
||||
void ClientConnection::set_main_mix_volume(int volume)
|
||||
{
|
||||
send_sync<Messages::AudioServer::SetMainMixVolume>(volume);
|
||||
}
|
||||
|
||||
int ClientConnection::get_remaining_samples()
|
||||
{
|
||||
return send_sync<Messages::AudioServer::GetRemainingSamples>()->remaining_samples();
|
||||
}
|
||||
|
||||
int ClientConnection::get_played_samples()
|
||||
{
|
||||
return send_sync<Messages::AudioServer::GetPlayedSamples>()->played_samples();
|
||||
}
|
||||
|
||||
void ClientConnection::set_paused(bool paused)
|
||||
{
|
||||
send_sync<Messages::AudioServer::SetPaused>(paused);
|
||||
}
|
||||
|
||||
void ClientConnection::clear_buffer(bool paused)
|
||||
{
|
||||
send_sync<Messages::AudioServer::ClearBuffer>(paused);
|
||||
}
|
||||
|
||||
int ClientConnection::get_playing_buffer()
|
||||
{
|
||||
return send_sync<Messages::AudioServer::GetPlayingBuffer>()->buffer_id();
|
||||
}
|
||||
|
||||
void ClientConnection::handle(const Messages::AudioClient::FinishedPlayingBuffer& message)
|
||||
{
|
||||
if (on_finish_playing_buffer)
|
||||
on_finish_playing_buffer(message.buffer_id());
|
||||
}
|
||||
|
||||
void ClientConnection::handle(const Messages::AudioClient::MutedStateChanged& message)
|
||||
{
|
||||
if (on_muted_state_change)
|
||||
on_muted_state_change(message.muted());
|
||||
}
|
||||
|
||||
void ClientConnection::handle(const Messages::AudioClient::MainMixVolumeChanged& message)
|
||||
{
|
||||
if (on_main_mix_volume_change)
|
||||
on_main_mix_volume_change(message.volume());
|
||||
}
|
||||
|
||||
}
|
70
Userland/Libraries/LibAudio/ClientConnection.h
Normal file
70
Userland/Libraries/LibAudio/ClientConnection.h
Normal file
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AudioServer/AudioClientEndpoint.h>
|
||||
#include <AudioServer/AudioServerEndpoint.h>
|
||||
#include <LibIPC/ServerConnection.h>
|
||||
|
||||
namespace Audio {
|
||||
|
||||
class Buffer;
|
||||
|
||||
class ClientConnection : public IPC::ServerConnection<AudioClientEndpoint, AudioServerEndpoint>
|
||||
, public AudioClientEndpoint {
|
||||
C_OBJECT(ClientConnection)
|
||||
public:
|
||||
ClientConnection();
|
||||
|
||||
virtual void handshake() override;
|
||||
void enqueue(const Buffer&);
|
||||
bool try_enqueue(const Buffer&);
|
||||
|
||||
bool get_muted();
|
||||
void set_muted(bool);
|
||||
|
||||
int get_main_mix_volume();
|
||||
void set_main_mix_volume(int);
|
||||
|
||||
int get_remaining_samples();
|
||||
int get_played_samples();
|
||||
int get_playing_buffer();
|
||||
|
||||
void set_paused(bool paused);
|
||||
void clear_buffer(bool paused = false);
|
||||
|
||||
Function<void(i32 buffer_id)> on_finish_playing_buffer;
|
||||
Function<void(bool muted)> on_muted_state_change;
|
||||
Function<void(int volume)> on_main_mix_volume_change;
|
||||
|
||||
private:
|
||||
virtual void handle(const Messages::AudioClient::FinishedPlayingBuffer&) override;
|
||||
virtual void handle(const Messages::AudioClient::MutedStateChanged&) override;
|
||||
virtual void handle(const Messages::AudioClient::MainMixVolumeChanged&) override;
|
||||
};
|
||||
|
||||
}
|
47
Userland/Libraries/LibAudio/Loader.cpp
Normal file
47
Userland/Libraries/LibAudio/Loader.cpp
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, the SerenityOS developers.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <LibAudio/WavLoader.h>
|
||||
|
||||
namespace Audio {
|
||||
|
||||
Loader::Loader(const StringView& path)
|
||||
{
|
||||
m_plugin = make<WavLoaderPlugin>(path);
|
||||
if (m_plugin->sniff())
|
||||
return;
|
||||
m_plugin = nullptr;
|
||||
}
|
||||
|
||||
Loader::Loader(const ByteBuffer& buffer)
|
||||
{
|
||||
m_plugin = make<WavLoaderPlugin>(buffer);
|
||||
if (m_plugin->sniff())
|
||||
return;
|
||||
m_plugin = nullptr;
|
||||
}
|
||||
|
||||
}
|
94
Userland/Libraries/LibAudio/Loader.h
Normal file
94
Userland/Libraries/LibAudio/Loader.h
Normal file
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, the SerenityOS developers.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/ByteBuffer.h>
|
||||
#include <AK/RefCounted.h>
|
||||
#include <AK/RefPtr.h>
|
||||
#include <AK/StringView.h>
|
||||
#include <LibCore/File.h>
|
||||
|
||||
namespace Audio {
|
||||
|
||||
class LoaderPlugin {
|
||||
public:
|
||||
virtual ~LoaderPlugin() { }
|
||||
|
||||
virtual bool sniff() = 0;
|
||||
|
||||
virtual bool has_error() { return false; }
|
||||
virtual const char* error_string() { return ""; }
|
||||
|
||||
virtual RefPtr<Buffer> get_more_samples(size_t max_bytes_to_read_from_input = 128 * KiB) = 0;
|
||||
|
||||
virtual void reset() = 0;
|
||||
virtual void seek(const int position) = 0;
|
||||
|
||||
virtual int loaded_samples() = 0;
|
||||
virtual int total_samples() = 0;
|
||||
virtual u32 sample_rate() = 0;
|
||||
virtual u16 num_channels() = 0;
|
||||
virtual u16 bits_per_sample() = 0;
|
||||
virtual RefPtr<Core::File> file() = 0;
|
||||
};
|
||||
|
||||
class Loader : public RefCounted<Loader> {
|
||||
public:
|
||||
static NonnullRefPtr<Loader> create(const StringView& path) { return adopt(*new Loader(path)); }
|
||||
static NonnullRefPtr<Loader> create(const ByteBuffer& buffer) { return adopt(*new Loader(buffer)); }
|
||||
|
||||
bool has_error() const { return m_plugin ? m_plugin->has_error() : true; }
|
||||
const char* error_string() const { return m_plugin ? m_plugin->error_string() : "No loader plugin available"; }
|
||||
|
||||
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 ? m_plugin->bits_per_sample() : 0; }
|
||||
RefPtr<Core::File> file() const { return m_plugin ? m_plugin->file() : nullptr; }
|
||||
|
||||
private:
|
||||
Loader(const StringView& path);
|
||||
Loader(const ByteBuffer& buffer);
|
||||
|
||||
mutable OwnPtr<LoaderPlugin> m_plugin;
|
||||
};
|
||||
|
||||
}
|
278
Userland/Libraries/LibAudio/WavLoader.cpp
Normal file
278
Userland/Libraries/LibAudio/WavLoader.cpp
Normal file
|
@ -0,0 +1,278 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <AK/NumericLimits.h>
|
||||
#include <AK/OwnPtr.h>
|
||||
#include <LibAudio/Buffer.h>
|
||||
#include <LibAudio/WavLoader.h>
|
||||
#include <LibCore/File.h>
|
||||
#include <LibCore/IODeviceStreamReader.h>
|
||||
|
||||
namespace Audio {
|
||||
|
||||
WavLoaderPlugin::WavLoaderPlugin(const StringView& path)
|
||||
: m_file(Core::File::construct(path))
|
||||
{
|
||||
if (!m_file->open(Core::IODevice::ReadOnly)) {
|
||||
m_error_string = String::formatted("Can't open file: {}", m_file->error_string());
|
||||
return;
|
||||
}
|
||||
|
||||
valid = parse_header();
|
||||
if (!valid)
|
||||
return;
|
||||
|
||||
m_resampler = make<ResampleHelper>(m_sample_rate, 44100);
|
||||
}
|
||||
|
||||
WavLoaderPlugin::WavLoaderPlugin(const ByteBuffer& buffer)
|
||||
{
|
||||
m_stream = make<InputMemoryStream>(buffer);
|
||||
if (!m_stream) {
|
||||
m_error_string = String::formatted("Can't open memory stream");
|
||||
return;
|
||||
}
|
||||
|
||||
valid = parse_header();
|
||||
if (!valid)
|
||||
return;
|
||||
|
||||
m_resampler = make<ResampleHelper>(m_sample_rate, 44100);
|
||||
}
|
||||
|
||||
bool WavLoaderPlugin::sniff()
|
||||
{
|
||||
return valid;
|
||||
}
|
||||
|
||||
RefPtr<Buffer> WavLoaderPlugin::get_more_samples(size_t max_bytes_to_read_from_input)
|
||||
{
|
||||
#ifdef AWAVLOADER_DEBUG
|
||||
dbgln("Read WAV of format PCM with num_channels {} sample rate {}, bits per sample {}", m_num_channels, m_sample_rate, m_bits_per_sample);
|
||||
#endif
|
||||
size_t samples_to_read = static_cast<int>(max_bytes_to_read_from_input) / (m_num_channels * (m_bits_per_sample / 8));
|
||||
RefPtr<Buffer> buffer;
|
||||
if (m_file) {
|
||||
auto raw_samples = m_file->read(max_bytes_to_read_from_input);
|
||||
if (raw_samples.is_empty())
|
||||
return nullptr;
|
||||
buffer = Buffer::from_pcm_data(raw_samples, *m_resampler, m_num_channels, m_bits_per_sample);
|
||||
} else {
|
||||
buffer = Buffer::from_pcm_stream(*m_stream, *m_resampler, m_num_channels, m_bits_per_sample, samples_to_read);
|
||||
}
|
||||
//Buffer contains normalized samples, but m_loaded_samples should contain the amount of actually loaded samples
|
||||
m_loaded_samples += samples_to_read;
|
||||
m_loaded_samples = min(m_total_samples, m_loaded_samples);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void WavLoaderPlugin::seek(const int position)
|
||||
{
|
||||
if (position < 0 || position > m_total_samples)
|
||||
return;
|
||||
|
||||
m_loaded_samples = position;
|
||||
size_t byte_position = position * m_num_channels * (m_bits_per_sample / 8);
|
||||
|
||||
if (m_file)
|
||||
m_file->seek(byte_position);
|
||||
else
|
||||
m_stream->seek(byte_position);
|
||||
}
|
||||
|
||||
void WavLoaderPlugin::reset()
|
||||
{
|
||||
seek(0);
|
||||
}
|
||||
|
||||
bool WavLoaderPlugin::parse_header()
|
||||
{
|
||||
OwnPtr<Core::IODeviceStreamReader> file_stream;
|
||||
bool ok = true;
|
||||
|
||||
if (m_file)
|
||||
file_stream = make<Core::IODeviceStreamReader>(*m_file);
|
||||
|
||||
auto read_u8 = [&]() -> u8 {
|
||||
u8 value;
|
||||
if (m_file) {
|
||||
*file_stream >> value;
|
||||
if (file_stream->handle_read_failure())
|
||||
ok = false;
|
||||
} else {
|
||||
*m_stream >> value;
|
||||
if (m_stream->has_any_error())
|
||||
ok = false;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
auto read_u16 = [&]() -> u16 {
|
||||
u16 value;
|
||||
if (m_file) {
|
||||
*file_stream >> value;
|
||||
if (file_stream->handle_read_failure())
|
||||
ok = false;
|
||||
} else {
|
||||
*m_stream >> value;
|
||||
if (m_stream->has_any_error())
|
||||
ok = false;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
auto read_u32 = [&]() -> u32 {
|
||||
u32 value;
|
||||
if (m_file) {
|
||||
*file_stream >> value;
|
||||
if (file_stream->handle_read_failure())
|
||||
ok = false;
|
||||
} else {
|
||||
*m_stream >> value;
|
||||
if (m_stream->has_any_error())
|
||||
ok = false;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
#define CHECK_OK(msg) \
|
||||
do { \
|
||||
if (!ok) { \
|
||||
m_error_string = String::formatted("Parsing failed: {}", msg); \
|
||||
return {}; \
|
||||
} \
|
||||
} while (0);
|
||||
|
||||
u32 riff = read_u32();
|
||||
ok = ok && riff == 0x46464952; // "RIFF"
|
||||
CHECK_OK("RIFF header");
|
||||
|
||||
u32 sz = read_u32();
|
||||
ok = ok && sz < 1024 * 1024 * 1024; // arbitrary
|
||||
CHECK_OK("File size");
|
||||
ASSERT(sz < 1024 * 1024 * 1024);
|
||||
|
||||
u32 wave = read_u32();
|
||||
ok = ok && wave == 0x45564157; // "WAVE"
|
||||
CHECK_OK("WAVE header");
|
||||
|
||||
u32 fmt_id = read_u32();
|
||||
ok = ok && fmt_id == 0x20746D66; // "FMT"
|
||||
CHECK_OK("FMT header");
|
||||
|
||||
u32 fmt_size = read_u32();
|
||||
ok = ok && fmt_size == 16;
|
||||
CHECK_OK("FMT size");
|
||||
ASSERT(fmt_size == 16);
|
||||
|
||||
u16 audio_format = read_u16();
|
||||
CHECK_OK("Audio format"); // incomplete read check
|
||||
ok = ok && audio_format == 1; // WAVE_FORMAT_PCM
|
||||
ASSERT(audio_format == 1);
|
||||
CHECK_OK("Audio format"); // value check
|
||||
|
||||
m_num_channels = read_u16();
|
||||
ok = ok && (m_num_channels == 1 || m_num_channels == 2);
|
||||
CHECK_OK("Channel count");
|
||||
|
||||
m_sample_rate = read_u32();
|
||||
CHECK_OK("Sample rate");
|
||||
|
||||
read_u32();
|
||||
CHECK_OK("Byte rate");
|
||||
|
||||
read_u16();
|
||||
CHECK_OK("Block align");
|
||||
|
||||
m_bits_per_sample = read_u16();
|
||||
CHECK_OK("Bits per sample"); // incomplete read check
|
||||
ok = ok && (m_bits_per_sample == 8 || m_bits_per_sample == 16 || m_bits_per_sample == 24);
|
||||
ASSERT(m_bits_per_sample == 8 || m_bits_per_sample == 16 || m_bits_per_sample == 24);
|
||||
CHECK_OK("Bits per sample"); // value check
|
||||
|
||||
// Read chunks until we find DATA
|
||||
bool found_data = false;
|
||||
u32 data_sz = 0;
|
||||
u8 search_byte = 0;
|
||||
while (true) {
|
||||
search_byte = read_u8();
|
||||
CHECK_OK("Reading byte searching for data");
|
||||
if (search_byte != 0x64) //D
|
||||
continue;
|
||||
|
||||
search_byte = read_u8();
|
||||
CHECK_OK("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");
|
||||
if (search_remaining != 0x6174) //TA
|
||||
continue;
|
||||
|
||||
data_sz = read_u32();
|
||||
found_data = true;
|
||||
break;
|
||||
}
|
||||
|
||||
ok = ok && found_data;
|
||||
CHECK_OK("Found no data chunk");
|
||||
ASSERT(found_data);
|
||||
|
||||
ok = ok && data_sz < INT32_MAX;
|
||||
CHECK_OK("Data was too large");
|
||||
|
||||
int bytes_per_sample = (m_bits_per_sample / 8) * m_num_channels;
|
||||
m_total_samples = data_sz / bytes_per_sample;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ResampleHelper::ResampleHelper(double source, double target)
|
||||
: m_ratio(source / target)
|
||||
{
|
||||
}
|
||||
|
||||
void ResampleHelper::process_sample(double sample_l, double sample_r)
|
||||
{
|
||||
m_last_sample_l = sample_l;
|
||||
m_last_sample_r = sample_r;
|
||||
m_current_ratio += 1;
|
||||
}
|
||||
|
||||
bool ResampleHelper::read_sample(double& next_l, double& next_r)
|
||||
{
|
||||
if (m_current_ratio > 0) {
|
||||
m_current_ratio -= m_ratio;
|
||||
next_l = m_last_sample_l;
|
||||
next_r = m_last_sample_r;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
82
Userland/Libraries/LibAudio/WavLoader.h
Normal file
82
Userland/Libraries/LibAudio/WavLoader.h
Normal file
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/ByteBuffer.h>
|
||||
#include <AK/MemoryStream.h>
|
||||
#include <AK/OwnPtr.h>
|
||||
#include <AK/RefPtr.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/StringView.h>
|
||||
#include <LibAudio/Buffer.h>
|
||||
#include <LibAudio/Loader.h>
|
||||
#include <LibCore/File.h>
|
||||
|
||||
namespace Audio {
|
||||
class Buffer;
|
||||
|
||||
// Parses a WAV file and produces an Audio::Buffer.
|
||||
class WavLoaderPlugin : public LoaderPlugin {
|
||||
public:
|
||||
WavLoaderPlugin(const StringView& path);
|
||||
WavLoaderPlugin(const ByteBuffer& buffer);
|
||||
|
||||
virtual bool sniff() override;
|
||||
|
||||
virtual bool has_error() override { return !m_error_string.is_null(); }
|
||||
virtual const char* error_string() override { return m_error_string.characters(); }
|
||||
|
||||
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 int loaded_samples() override { return m_loaded_samples; }
|
||||
virtual int total_samples() override { return m_total_samples; }
|
||||
virtual u32 sample_rate() override { return m_sample_rate; }
|
||||
virtual u16 num_channels() override { return m_num_channels; }
|
||||
virtual u16 bits_per_sample() override { return m_bits_per_sample; }
|
||||
virtual RefPtr<Core::File> file() override { return m_file; }
|
||||
|
||||
private:
|
||||
bool parse_header();
|
||||
|
||||
bool valid { false };
|
||||
RefPtr<Core::File> m_file;
|
||||
OwnPtr<InputMemoryStream> m_stream;
|
||||
String m_error_string;
|
||||
OwnPtr<ResampleHelper> m_resampler;
|
||||
|
||||
u32 m_sample_rate { 0 };
|
||||
u16 m_num_channels { 0 };
|
||||
u16 m_bits_per_sample { 0 };
|
||||
|
||||
int m_loaded_samples { 0 };
|
||||
int m_total_samples { 0 };
|
||||
};
|
||||
|
||||
}
|
126
Userland/Libraries/LibAudio/WavWriter.cpp
Normal file
126
Userland/Libraries/LibAudio/WavWriter.cpp
Normal file
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* Copyright (c) 2020, William McPherson <willmcpherson2@gmail.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <LibAudio/WavWriter.h>
|
||||
|
||||
namespace Audio {
|
||||
|
||||
WavWriter::WavWriter(const StringView& path, int sample_rate, int num_channels, int bits_per_sample)
|
||||
: m_sample_rate(sample_rate)
|
||||
, m_num_channels(num_channels)
|
||||
, m_bits_per_sample(bits_per_sample)
|
||||
{
|
||||
set_file(path);
|
||||
}
|
||||
|
||||
WavWriter::WavWriter(int sample_rate, int num_channels, int bits_per_sample)
|
||||
: m_sample_rate(sample_rate)
|
||||
, m_num_channels(num_channels)
|
||||
, m_bits_per_sample(bits_per_sample)
|
||||
{
|
||||
}
|
||||
|
||||
WavWriter::~WavWriter()
|
||||
{
|
||||
if (!m_finalized)
|
||||
finalize();
|
||||
}
|
||||
|
||||
void WavWriter::set_file(const StringView& path)
|
||||
{
|
||||
m_file = Core::File::construct(path);
|
||||
if (!m_file->open(Core::IODevice::ReadWrite)) {
|
||||
m_error_string = String::formatted("Can't open file: {}", m_file->error_string());
|
||||
return;
|
||||
}
|
||||
m_file->seek(44);
|
||||
m_finalized = false;
|
||||
}
|
||||
|
||||
void WavWriter::write_samples(const u8* samples, size_t size)
|
||||
{
|
||||
m_data_sz += size;
|
||||
m_file->write(samples, size);
|
||||
}
|
||||
|
||||
void WavWriter::finalize()
|
||||
{
|
||||
ASSERT(!m_finalized);
|
||||
m_finalized = true;
|
||||
if (m_file) {
|
||||
m_file->seek(0);
|
||||
write_header();
|
||||
m_file->close();
|
||||
}
|
||||
m_data_sz = 0;
|
||||
}
|
||||
|
||||
void WavWriter::write_header()
|
||||
{
|
||||
// "RIFF"
|
||||
static u32 riff = 0x46464952;
|
||||
m_file->write(reinterpret_cast<u8*>(&riff), sizeof(riff));
|
||||
|
||||
// Size of data + (size of header - previous field - this field)
|
||||
u32 sz = m_data_sz + (44 - 4 - 4);
|
||||
m_file->write(reinterpret_cast<u8*>(&sz), sizeof(sz));
|
||||
|
||||
// "WAVE"
|
||||
static u32 wave = 0x45564157;
|
||||
m_file->write(reinterpret_cast<u8*>(&wave), sizeof(wave));
|
||||
|
||||
// "fmt "
|
||||
static u32 fmt_id = 0x20746D66;
|
||||
m_file->write(reinterpret_cast<u8*>(&fmt_id), sizeof(fmt_id));
|
||||
|
||||
// Size of the next 6 fields
|
||||
static u32 fmt_size = 16;
|
||||
m_file->write(reinterpret_cast<u8*>(&fmt_size), sizeof(fmt_size));
|
||||
|
||||
// 1 for PCM
|
||||
static u16 audio_format = 1;
|
||||
m_file->write(reinterpret_cast<u8*>(&audio_format), sizeof(audio_format));
|
||||
|
||||
m_file->write(reinterpret_cast<u8*>(&m_num_channels), sizeof(m_num_channels));
|
||||
|
||||
m_file->write(reinterpret_cast<u8*>(&m_sample_rate), sizeof(m_sample_rate));
|
||||
|
||||
u32 byte_rate = m_sample_rate * m_num_channels * (m_bits_per_sample / 8);
|
||||
m_file->write(reinterpret_cast<u8*>(&byte_rate), sizeof(byte_rate));
|
||||
|
||||
u16 block_align = m_num_channels * (m_bits_per_sample / 8);
|
||||
m_file->write(reinterpret_cast<u8*>(&block_align), sizeof(block_align));
|
||||
|
||||
m_file->write(reinterpret_cast<u8*>(&m_bits_per_sample), sizeof(m_bits_per_sample));
|
||||
|
||||
// "data"
|
||||
static u32 chunk_id = 0x61746164;
|
||||
m_file->write(reinterpret_cast<u8*>(&chunk_id), sizeof(chunk_id));
|
||||
|
||||
m_file->write(reinterpret_cast<u8*>(&m_data_sz), sizeof(m_data_sz));
|
||||
}
|
||||
|
||||
}
|
70
Userland/Libraries/LibAudio/WavWriter.h
Normal file
70
Userland/Libraries/LibAudio/WavWriter.h
Normal file
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Copyright (c) 2020, William McPherson <willmcpherson2@gmail.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/StringView.h>
|
||||
#include <LibCore/File.h>
|
||||
|
||||
namespace Audio {
|
||||
|
||||
class WavWriter {
|
||||
public:
|
||||
WavWriter(const StringView& path, int sample_rate = 44100, int num_channels = 2, int bits_per_sample = 16);
|
||||
WavWriter(int sample_rate = 44100, int num_channels = 2, int bits_per_sample = 16);
|
||||
~WavWriter();
|
||||
|
||||
bool has_error() const { return !m_error_string.is_null(); }
|
||||
const char* error_string() const { return m_error_string.characters(); }
|
||||
|
||||
void write_samples(const u8* samples, size_t size);
|
||||
void finalize(); // You can finalize manually or let the destructor do it.
|
||||
|
||||
u32 sample_rate() const { return m_sample_rate; }
|
||||
u16 num_channels() const { return m_num_channels; }
|
||||
u16 bits_per_sample() const { return m_bits_per_sample; }
|
||||
RefPtr<Core::File> file() const { return m_file; }
|
||||
|
||||
void set_file(const StringView& path);
|
||||
void set_num_channels(int num_channels) { m_num_channels = num_channels; }
|
||||
void set_sample_rate(int sample_rate) { m_sample_rate = sample_rate; }
|
||||
void set_bits_per_sample(int bits_per_sample) { m_bits_per_sample = bits_per_sample; }
|
||||
|
||||
void clear_error() { m_error_string = String(); }
|
||||
|
||||
private:
|
||||
void write_header();
|
||||
RefPtr<Core::File> m_file;
|
||||
String m_error_string;
|
||||
bool m_finalized { false };
|
||||
|
||||
u32 m_sample_rate;
|
||||
u16 m_num_channels;
|
||||
u16 m_bits_per_sample;
|
||||
u32 m_data_sz { 0 };
|
||||
};
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue