1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 10:27:35 +00:00

Audio: Make basic streaming WAV playback work.

I had to solve a bunch of things simultaneously to make this work.
Refactor AWavLoader to be a streaming loader rather than a one-shot one.
The constructor parses the header, and if everything looks good, you can
repeatedly ask the AWavLoader for sample buffers until it runs out.

Also send a message from AudioServer when a buffer has finished playing.
That allows us to implement a blocking variant of play().

Use all of this in aplay to play WAV files chunk-at-a-time.
This is definitely not perfect and it's a little glitchy and skippy,
but I think it's a step in the right direction.
This commit is contained in:
Andreas Kling 2019-07-27 17:20:41 +02:00
parent a292d8cd5a
commit 426248098c
10 changed files with 88 additions and 64 deletions

View file

@ -53,9 +53,10 @@ struct ASample {
class ABuffer : public RefCounted<ABuffer> {
public:
static RefPtr<ABuffer> from_pcm_data(ByteBuffer& data, int num_channels, int bits_per_sample, int source_rate);
ABuffer(Vector<ASample>&& samples)
: m_samples(move(samples))
{}
static NonnullRefPtr<ABuffer> create_with_samples(Vector<ASample>&& samples)
{
return adopt(*new ABuffer(move(samples)));
}
const Vector<ASample>& samples() const { return m_samples; }
Vector<ASample>& samples() { return m_samples; }
@ -63,6 +64,10 @@ public:
int size_in_bytes() const { return m_samples.size() * sizeof(ASample); }
private:
ABuffer(Vector<ASample>&& samples)
: m_samples(move(samples))
{
}
Vector<ASample> m_samples;
};

View file

@ -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);
}

View file

@ -11,5 +11,5 @@ public:
AClientConnection();
virtual void handshake() override;
void play(const ABuffer&);
void play(const ABuffer&, bool block);
};

View file

@ -5,6 +5,7 @@ struct ASAPI_ServerMessage {
Invalid,
Greeting,
PlayingBuffer,
FinishedPlayingBuffer,
};
Type type { Type::Invalid };

View file

@ -2,26 +2,32 @@
#include <LibAudio/ABuffer.h>
#include <LibAudio/AWavLoader.h>
#include <LibCore/CFile.h>
#include <LibCore/CFileStreamReader.h>
#include <limits>
RefPtr<ABuffer> 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<ABuffer> AWavLoader::parse_wav(ByteBuffer& buffer)
RefPtr<ABuffer> 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<ABuffer> 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<ABuffer> 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<ABuffer> AWavLoader::parse_wav(ByteBuffer& buffer)
CHECK_OK("Found no data chunk");
ASSERT(found_data);
ok = ok && data_sz < std::numeric_limits<int>::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

View file

@ -3,6 +3,7 @@
#include <AK/AKString.h>
#include <AK/RefPtr.h>
#include <AK/StringView.h>
#include <LibCore/CFile.h>
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<ABuffer> load_wav(const StringView& path);
const char* error_string() { return m_error_string.characters(); }
RefPtr<ABuffer> get_more_samples();
private:
RefPtr<ABuffer> 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 };
};