diff --git a/Libraries/LibAudio/ABuffer.h b/Libraries/LibAudio/ABuffer.h index 1bae00e6c2..364c84d40a 100644 --- a/Libraries/LibAudio/ABuffer.h +++ b/Libraries/LibAudio/ABuffer.h @@ -53,9 +53,10 @@ struct ASample { class ABuffer : public RefCounted { public: static RefPtr from_pcm_data(ByteBuffer& data, int num_channels, int bits_per_sample, int source_rate); - ABuffer(Vector&& samples) - : m_samples(move(samples)) - {} + static NonnullRefPtr create_with_samples(Vector&& samples) + { + return adopt(*new ABuffer(move(samples))); + } const Vector& samples() const { return m_samples; } Vector& samples() { return m_samples; } @@ -63,6 +64,10 @@ public: int size_in_bytes() const { return m_samples.size() * sizeof(ASample); } private: + ABuffer(Vector&& samples) + : m_samples(move(samples)) + { + } + Vector m_samples; }; - diff --git a/Libraries/LibAudio/AClientConnection.cpp b/Libraries/LibAudio/AClientConnection.cpp index 8bf83c1db1..011f416172 100644 --- a/Libraries/LibAudio/AClientConnection.cpp +++ b/Libraries/LibAudio/AClientConnection.cpp @@ -17,7 +17,7 @@ void AClientConnection::handshake() set_my_client_id(response.greeting.your_client_id); } -void AClientConnection::play(const ABuffer& buffer) +void AClientConnection::play(const ABuffer& buffer, bool block) { auto shared_buf = SharedBuffer::create_with_size(buffer.size_in_bytes()); if (!shared_buf) { @@ -31,5 +31,5 @@ void AClientConnection::play(const ABuffer& buffer) ASAPI_ClientMessage request; request.type = ASAPI_ClientMessage::Type::PlayBuffer; request.play_buffer.buffer_id = shared_buf->shared_buffer_id(); - sync_request(request, ASAPI_ServerMessage::Type::PlayingBuffer); + sync_request(request, block ? ASAPI_ServerMessage::Type::FinishedPlayingBuffer : ASAPI_ServerMessage::Type::PlayingBuffer); } diff --git a/Libraries/LibAudio/AClientConnection.h b/Libraries/LibAudio/AClientConnection.h index 9f42538903..214af48bc0 100644 --- a/Libraries/LibAudio/AClientConnection.h +++ b/Libraries/LibAudio/AClientConnection.h @@ -11,5 +11,5 @@ public: AClientConnection(); virtual void handshake() override; - void play(const ABuffer&); + void play(const ABuffer&, bool block); }; diff --git a/Libraries/LibAudio/ASAPI.h b/Libraries/LibAudio/ASAPI.h index e1c3c0b09a..227aee1027 100644 --- a/Libraries/LibAudio/ASAPI.h +++ b/Libraries/LibAudio/ASAPI.h @@ -5,6 +5,7 @@ struct ASAPI_ServerMessage { Invalid, Greeting, PlayingBuffer, + FinishedPlayingBuffer, }; Type type { Type::Invalid }; diff --git a/Libraries/LibAudio/AWavLoader.cpp b/Libraries/LibAudio/AWavLoader.cpp index 3c17f719c5..f5ff5d7cc8 100644 --- a/Libraries/LibAudio/AWavLoader.cpp +++ b/Libraries/LibAudio/AWavLoader.cpp @@ -2,26 +2,32 @@ #include #include #include +#include #include -RefPtr AWavLoader::load_wav(const StringView& path) +AWavLoader::AWavLoader(const StringView& path) + : m_file(path) { - m_error_string = {}; - - CFile wav(path); - if (!wav.open(CIODevice::ReadOnly)) { - m_error_string = String::format("Can't open file: %s", wav.error_string()); - return nullptr; + if (!m_file.open(CIODevice::ReadOnly)) { + m_error_string = String::format("Can't open file: %s", m_file.error_string()); + return; } - auto contents = wav.read_all(); - return parse_wav(contents); + parse_header(); } -// TODO: A streaming parser might be better than forcing a ByteBuffer -RefPtr AWavLoader::parse_wav(ByteBuffer& buffer) +RefPtr AWavLoader::get_more_samples() { - BufferStream stream(buffer); + dbgprintf("Read WAV of format PCM with num_channels %u sample rate %u, bits per sample %u\n", m_num_channels, m_sample_rate, m_bits_per_sample); + + auto raw_samples = m_file.read(128 * KB); + auto buffer = ABuffer::from_pcm_data(raw_samples, m_num_channels, m_bits_per_sample, m_sample_rate); + return buffer; +} + +bool AWavLoader::parse_header() +{ + CFileStreamReader stream(m_file); #define CHECK_OK(msg) \ do { \ @@ -73,13 +79,11 @@ RefPtr AWavLoader::parse_wav(ByteBuffer& buffer) ASSERT(audio_format == 1); CHECK_OK("Audio format"); // value check - u16 num_channels; - stream >> num_channels; - ok = ok && (num_channels == 1 || num_channels == 2); + stream >> m_num_channels; + ok = ok && (m_num_channels == 1 || m_num_channels == 2); CHECK_OK("Channel count"); - u32 sample_rate; - stream >> sample_rate; + stream >> m_sample_rate; CHECK_OK("Sample rate"); u32 byte_rate; @@ -90,11 +94,10 @@ RefPtr AWavLoader::parse_wav(ByteBuffer& buffer) stream >> block_align; CHECK_OK("Block align"); - u16 bits_per_sample; - stream >> bits_per_sample; + stream >> m_bits_per_sample; CHECK_OK("Bits per sample"); // incomplete read check - ok = ok && (bits_per_sample == 8 || bits_per_sample == 16 || bits_per_sample == 24); - ASSERT(bits_per_sample == 8 || bits_per_sample == 16 || bits_per_sample == 24); + 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 @@ -116,21 +119,13 @@ RefPtr AWavLoader::parse_wav(ByteBuffer& buffer) CHECK_OK("Found no data chunk"); ASSERT(found_data); - ok = ok && data_sz < std::numeric_limits::max(); + ok = ok && data_sz < INT32_MAX; CHECK_OK("Data was too large"); - // ### consider using BufferStream to do this for us - ok = ok && int(data_sz) <= (buffer.size() - stream.offset()); - CHECK_OK("Bad DATA (truncated)"); - // Just make sure we're good before we read the data... ASSERT(!stream.handle_read_failure()); - auto sample_data = buffer.slice_view(stream.offset(), data_sz); - - dbgprintf("Read WAV of format PCM with num_channels %d sample rate %d, bits per sample %d\n", num_channels, sample_rate, bits_per_sample); - - return ABuffer::from_pcm_data(sample_data, num_channels, bits_per_sample, sample_rate); + return true; } // Small helper to resample from one playback rate to another diff --git a/Libraries/LibAudio/AWavLoader.h b/Libraries/LibAudio/AWavLoader.h index 09722bd2f4..d1d30a3884 100644 --- a/Libraries/LibAudio/AWavLoader.h +++ b/Libraries/LibAudio/AWavLoader.h @@ -3,6 +3,7 @@ #include #include #include +#include class ABuffer; @@ -13,10 +14,18 @@ class ByteBuffer; // Parses a WAV file and produces an ABuffer instance from it class AWavLoader { public: + explicit AWavLoader(const StringView& path); RefPtr load_wav(const StringView& path); const char* error_string() { return m_error_string.characters(); } + RefPtr get_more_samples(); + private: - RefPtr parse_wav(ByteBuffer&); + bool parse_header(); + CFile m_file; String m_error_string; + + u32 m_sample_rate { 0 }; + u16 m_num_channels { 0 }; + u16 m_bits_per_sample { 0 }; }; diff --git a/Servers/AudioServer/ASClientConnection.cpp b/Servers/AudioServer/ASClientConnection.cpp index 1989c166d3..91351e6210 100644 --- a/Servers/AudioServer/ASClientConnection.cpp +++ b/Servers/AudioServer/ASClientConnection.cpp @@ -48,11 +48,6 @@ bool ASClientConnection::handle_message(const ASAPI_ClientMessage& message, cons did_misbehave(); return false; } - - if (shared_buf->size() / sizeof(ASample) > 441000) { - did_misbehave(); - return false; - } samples.resize(shared_buf->size() / sizeof(ASample)); memcpy(samples.data(), shared_buf->data(), shared_buf->size()); } @@ -64,7 +59,7 @@ bool ASClientConnection::handle_message(const ASAPI_ClientMessage& message, cons reply.playing_buffer.buffer_id = message.play_buffer.buffer_id; post_message(reply); - m_mixer.queue(*this, adopt(*new ABuffer(move(samples)))); + m_mixer.queue(*this, ABuffer::create_with_samples(move(samples)), message.play_buffer.buffer_id); break; } case ASAPI_ClientMessage::Type::Invalid: diff --git a/Servers/AudioServer/ASMixer.cpp b/Servers/AudioServer/ASMixer.cpp index 2881f1717c..472d126cec 100644 --- a/Servers/AudioServer/ASMixer.cpp +++ b/Servers/AudioServer/ASMixer.cpp @@ -1,8 +1,9 @@ #include +#include +#include +#include #include - #include -#include "ASMixer.h" ASMixer::ASMixer() : m_device("/dev/audio") @@ -19,11 +20,11 @@ ASMixer::ASMixer() }, this); } -void ASMixer::queue(ASClientConnection&, const ABuffer& buffer) +void ASMixer::queue(ASClientConnection& client, const ABuffer& buffer, int buffer_id) { ASSERT(buffer.size_in_bytes()); CLocker lock(m_lock); - m_pending_mixing.append(ASMixerBuffer(buffer)); + m_pending_mixing.append(ASMixerBuffer(buffer, client, buffer_id)); } void ASMixer::mix() @@ -75,8 +76,15 @@ void ASMixer::mix() } // clear it later - if (buffer.pos == samples.size()) + if (buffer.pos == samples.size()) { + if (buffer.m_buffer_id && buffer.m_client) { + ASAPI_ServerMessage reply; + reply.type = ASAPI_ServerMessage::Type::FinishedPlayingBuffer; + reply.playing_buffer.buffer_id = buffer.m_buffer_id; + buffer.m_client->post_message(reply); + } buffer.done = true; + } } // output the mixed stuff to the device @@ -108,3 +116,10 @@ void ASMixer::mix() } } } + +ASMixer::ASMixerBuffer::ASMixerBuffer(const NonnullRefPtr& buf, ASClientConnection& client, int buffer_id) + : buffer(buf) + , m_client(client.make_weak_ptr()) + , m_buffer_id(buffer_id) +{ +} diff --git a/Servers/AudioServer/ASMixer.h b/Servers/AudioServer/ASMixer.h index 2cf45fd13f..8f39f42668 100644 --- a/Servers/AudioServer/ASMixer.h +++ b/Servers/AudioServer/ASMixer.h @@ -1,11 +1,12 @@ #pragma once -#include #include +#include +#include +#include +#include #include #include -#include -#include class ASClientConnection; @@ -13,16 +14,16 @@ class ASMixer : public RefCounted { public: ASMixer(); - void queue(ASClientConnection&, const ABuffer&); + void queue(ASClientConnection&, const ABuffer&, int buffer_id); private: struct ASMixerBuffer { - ASMixerBuffer(const NonnullRefPtr& buf) - : buffer(buf) - {} + ASMixerBuffer(const NonnullRefPtr&, ASClientConnection&, int buffer_id = -1); NonnullRefPtr buffer; int pos { 0 }; bool done { false }; + WeakPtr m_client; + int m_buffer_id { -1 }; }; Vector m_pending_mixing; diff --git a/Userland/aplay.cpp b/Userland/aplay.cpp index 8d8ef71d56..a86088c55c 100644 --- a/Userland/aplay.cpp +++ b/Userland/aplay.cpp @@ -16,15 +16,18 @@ int main(int argc, char **argv) AClientConnection a_conn; a_conn.handshake(); printf("Established connection\n"); - AWavLoader loader; - const auto& buffer = loader.load_wav(argv[1]); - if (!buffer) { - dbgprintf("Can't parse WAV: %s\n", loader.error_string()); - return 1; + AWavLoader loader(argv[1]); + printf("Loaded WAV\n"); + + for (;;) { + auto samples = loader.get_more_samples(); + if (!samples) { + break; + } + printf("Playing %d sample(s)\n", samples->samples().size()); + a_conn.play(*samples, true); } - printf("Playing WAV\n"); - a_conn.play(*buffer); printf("Exiting! :)\n"); return 0; }