mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 03:47:35 +00:00
AudioServer: Assorted infrastructure work
* Add a LibAudio, and move WAV file parsing there (via AWavFile and AWavLoader) * Add CLocalSocket, and CSocket::connect() variant for local address types. We make some small use of this in WindowServer (as that's where we modelled it from), but don't get too invasive as this PR is already quite large, and the WS I/O is a bit carefully done * Add an AClientConnection which will eventually be used to talk to AudioServer (and make use of it in Piano, though right now it really doesn't do anything except connect, using our new CLocalSocket...)
This commit is contained in:
parent
983245113a
commit
ffa8cb668f
22 changed files with 431 additions and 162 deletions
26
Libraries/LibAudio/AClientConnection.cpp
Normal file
26
Libraries/LibAudio/AClientConnection.cpp
Normal file
|
@ -0,0 +1,26 @@
|
|||
#include "AClientConnection.h"
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
|
||||
AClientConnection::AClientConnection()
|
||||
{
|
||||
m_connection.on_connected = [this] {
|
||||
m_notifier = make<CNotifier>(m_connection.fd(), CNotifier::Read);
|
||||
m_notifier->on_ready_to_read = [this] { printf("AudioServer said something to us"); };
|
||||
m_connection.write("Hello, friends");
|
||||
};
|
||||
|
||||
int retries = 1000;
|
||||
while (retries) {
|
||||
if (m_connection.connect(CSocketAddress::local("/tmp/asportal"))) {
|
||||
break;
|
||||
}
|
||||
|
||||
#ifdef ACLIENT_DEBUG
|
||||
dbgprintf("AClientConnection: connect failed: %d, %s\n", errno, strerror(errno));
|
||||
#endif
|
||||
sleep(1);
|
||||
--retries;
|
||||
}
|
||||
}
|
||||
|
13
Libraries/LibAudio/AClientConnection.h
Normal file
13
Libraries/LibAudio/AClientConnection.h
Normal file
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include <LibCore/CLocalSocket.h>
|
||||
#include <LibCore/CNotifier.h>
|
||||
|
||||
class AClientConnection {
|
||||
public:
|
||||
AClientConnection();
|
||||
|
||||
private:
|
||||
CLocalSocket m_connection;
|
||||
OwnPtr<CNotifier> m_notifier;
|
||||
};
|
2
Libraries/LibAudio/AWavFile.cpp
Normal file
2
Libraries/LibAudio/AWavFile.cpp
Normal file
|
@ -0,0 +1,2 @@
|
|||
#include "AWavFile.h"
|
||||
|
32
Libraries/LibAudio/AWavFile.h
Normal file
32
Libraries/LibAudio/AWavFile.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
#pragma once
|
||||
|
||||
#include <AK/RefCounted.h>
|
||||
#include <AK/ByteBuffer.h>
|
||||
#include <AK/Types.h>
|
||||
|
||||
class AWavFile : public RefCounted<AWavFile> {
|
||||
public:
|
||||
enum class Format {
|
||||
Invalid,
|
||||
PCM,
|
||||
};
|
||||
|
||||
Format format() const { return m_format; }
|
||||
u16 channel_count() const { return m_channel_count; }
|
||||
u32 sample_rate_per_second() const { return m_sample_rate; }
|
||||
u32 average_byte_rate_per_second() const { return m_byte_rate; }
|
||||
u16 block_align() const { return m_block_align; }
|
||||
u16 bits_per_sample() const { return m_bits_per_sample; }
|
||||
const ByteBuffer& sample_data() const { return m_sample_data; }
|
||||
|
||||
private:
|
||||
Format m_format = Format::Invalid;
|
||||
u16 m_channel_count = 0;
|
||||
u32 m_sample_rate = 0;
|
||||
u32 m_byte_rate = 0;
|
||||
u16 m_block_align = 0;
|
||||
u16 m_bits_per_sample = 0;
|
||||
ByteBuffer m_sample_data;
|
||||
|
||||
friend class AWavLoader;
|
||||
};
|
166
Libraries/LibAudio/AWavLoader.cpp
Normal file
166
Libraries/LibAudio/AWavLoader.cpp
Normal file
|
@ -0,0 +1,166 @@
|
|||
#include <LibCore/CFile.h>
|
||||
|
||||
#include "AWavLoader.h"
|
||||
#include "AWavFile.h"
|
||||
|
||||
static u32 read_u32(const ByteBuffer& buf, u32& off)
|
||||
{
|
||||
ASSERT(buf.size() - off >= 4);
|
||||
u32 b0 = buf[off + 0];
|
||||
u32 b1 = buf[off + 1];
|
||||
u32 b2 = buf[off + 2];
|
||||
u32 b3 = buf[off + 3];
|
||||
|
||||
u32 ret = 0;
|
||||
ret |= (u8(b3) << 24);
|
||||
ret |= (u8(b2) << 16);
|
||||
ret |= (u8(b1) << 8);
|
||||
ret |= (u8(b0));
|
||||
|
||||
off += 4;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static u16 read_u16(const ByteBuffer& buf, u32& off)
|
||||
{
|
||||
ASSERT(buf.size() - off >= 2);
|
||||
u16 b0 = buf[off + 0];
|
||||
u16 b1 = buf[off + 1];
|
||||
|
||||
u16 ret = 0;
|
||||
ret |= (u8(b1) << 8);
|
||||
ret |= (u8(b0));
|
||||
|
||||
off += 2;
|
||||
return ret;
|
||||
}
|
||||
|
||||
RefPtr<AWavFile> AWavLoader::load_wav(const StringView& 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;
|
||||
}
|
||||
|
||||
const auto& contents = wav.read_all();
|
||||
return parse_wav(contents);
|
||||
}
|
||||
|
||||
// TODO: A streaming parser might be better than forcing a ByteBuffer
|
||||
RefPtr<AWavFile> AWavLoader::parse_wav(const ByteBuffer& buffer)
|
||||
{
|
||||
u32 off = 0;
|
||||
|
||||
if (buffer.size() - off < 12) {
|
||||
dbgprintf("WAV is too small (no header, %d bytes)\n", buffer.size());
|
||||
return {};
|
||||
}
|
||||
|
||||
dbgprintf("Trying to parse %d bytes of wav\n", buffer.size());
|
||||
|
||||
#define CHECK_OK(msg) \
|
||||
do { \
|
||||
ASSERT(ok); \
|
||||
if (!ok) { \
|
||||
m_error_string = String::format("Parsing failed: %s", msg); \
|
||||
return {}; \
|
||||
} else { \
|
||||
dbgprintf("%s is OK!\n", msg); \
|
||||
} \
|
||||
} while (0);
|
||||
|
||||
bool ok = true;
|
||||
u32 riff = read_u32(buffer, off);
|
||||
ok = ok && riff == 0x46464952; // "RIFF"
|
||||
CHECK_OK("RIFF header");
|
||||
|
||||
u32 sz = read_u32(buffer, off);
|
||||
ASSERT(sz < 1024 * 1024 * 42);
|
||||
ok = ok && sz < 1024 * 1024 * 42; // arbitrary
|
||||
CHECK_OK("File size");
|
||||
|
||||
u32 wave = read_u32(buffer, off);
|
||||
ok = ok && wave == 0x45564157; // "WAVE"
|
||||
CHECK_OK("WAVE header");
|
||||
|
||||
if (buffer.size() - off < 8) {
|
||||
dbgprintf("WAV is too small (no fmt, %d bytes)\n", buffer.size());
|
||||
return {};
|
||||
}
|
||||
|
||||
u32 fmt_id = read_u32(buffer, off);
|
||||
ok = ok && fmt_id == 0x20746D66; // "FMT"
|
||||
CHECK_OK("FMT header");
|
||||
|
||||
u32 fmt_size = read_u32(buffer, off);
|
||||
ok = ok && fmt_size == 16;
|
||||
ASSERT(fmt_size == 16);
|
||||
CHECK_OK("FMT size");
|
||||
|
||||
if (buffer.size() - off < 16) {
|
||||
dbgprintf("WAV is too small (fmt chunk, %d bytes)\n", buffer.size());
|
||||
return {};
|
||||
}
|
||||
|
||||
auto ret = adopt(*new AWavFile);
|
||||
u16 audio_format = read_u16(buffer, off);
|
||||
ok = ok && audio_format == 1; // WAVE_FORMAT_PCM
|
||||
ASSERT(audio_format == 1);
|
||||
CHECK_OK("Audio format");
|
||||
ret->m_format = AWavFile::Format::PCM;
|
||||
|
||||
u16 num_channels = read_u16(buffer, off);
|
||||
CHECK_OK("Channel count");
|
||||
ret->m_channel_count = num_channels;
|
||||
|
||||
u32 sample_rate = read_u32(buffer, off);
|
||||
CHECK_OK("Sample rate");
|
||||
ret->m_sample_rate = sample_rate;
|
||||
|
||||
u32 byte_rate = read_u32(buffer, off);
|
||||
CHECK_OK("Byte rate");
|
||||
ret->m_byte_rate = byte_rate;
|
||||
|
||||
u16 block_align = read_u16(buffer, off);
|
||||
CHECK_OK("Block align");
|
||||
ret->m_block_align = block_align;
|
||||
|
||||
u16 bits_per_sample = read_u16(buffer, off);
|
||||
ok = ok && (bits_per_sample == 8 || bits_per_sample == 16);
|
||||
ASSERT(bits_per_sample == 8 || bits_per_sample == 16);
|
||||
CHECK_OK("Bits per sample");
|
||||
ret->m_bits_per_sample = bits_per_sample;
|
||||
|
||||
// Read chunks until we find DATA
|
||||
if (off >= u32(buffer.size()) - 8) {
|
||||
ok = ok && false;
|
||||
ASSERT_NOT_REACHED();
|
||||
CHECK_OK("Premature EOF without DATA");
|
||||
}
|
||||
|
||||
bool found_data = false;
|
||||
u32 data_sz = 0;
|
||||
while (off < u32(buffer.size()) - 8) {
|
||||
u32 chunk_id = read_u32(buffer, off);
|
||||
data_sz = read_u32(buffer, off);
|
||||
if (chunk_id == 0x61746164) { // DATA
|
||||
found_data = true;
|
||||
break;
|
||||
}
|
||||
off += data_sz;
|
||||
}
|
||||
|
||||
ok = ok && found_data;
|
||||
ASSERT(found_data);
|
||||
CHECK_OK("Found no data chunk");
|
||||
|
||||
ok = ok && data_sz <= (buffer.size() - off);
|
||||
CHECK_OK("Bad DATA size");
|
||||
|
||||
ret->m_sample_data = buffer.slice(off, data_sz);
|
||||
return ret;
|
||||
}
|
||||
|
14
Libraries/LibAudio/AWavLoader.h
Normal file
14
Libraries/LibAudio/AWavLoader.h
Normal file
|
@ -0,0 +1,14 @@
|
|||
#pragma once
|
||||
|
||||
#include <AK/RefPtr.h>
|
||||
|
||||
class AWavFile;
|
||||
|
||||
class AWavLoader {
|
||||
public:
|
||||
RefPtr<AWavFile> load_wav(const StringView& path);
|
||||
const char* error_string() { return m_error_string.characters(); }
|
||||
private:
|
||||
RefPtr<AWavFile> parse_wav(const ByteBuffer& buffer);
|
||||
String m_error_string;
|
||||
};
|
32
Libraries/LibAudio/Makefile
Normal file
32
Libraries/LibAudio/Makefile
Normal file
|
@ -0,0 +1,32 @@
|
|||
include ../../Makefile.common
|
||||
|
||||
OBJS = \
|
||||
AClientConnection.o \
|
||||
AWavFile.o \
|
||||
AWavLoader.o
|
||||
|
||||
LIBRARY = libaudio.a
|
||||
DEFINES += -DUSERLAND
|
||||
|
||||
all: $(LIBRARY)
|
||||
|
||||
$(LIBRARY): $(OBJS)
|
||||
@echo "LIB $@"; $(AR) rcs $@ $(OBJS) $(LIBS)
|
||||
|
||||
.cpp.o:
|
||||
@echo "CXX $<"; $(CXX) $(CXXFLAGS) -o $@ -c $<
|
||||
|
||||
-include $(OBJS:%.o=%.d)
|
||||
|
||||
clean:
|
||||
@echo "CLEAN"; rm -f $(LIBRARY) $(OBJS) *.d
|
||||
|
||||
install: $(LIBRARY)
|
||||
mkdir -p ../../Root/usr/include/LibAudio
|
||||
mkdir -p ../../Root/usr/include/AK
|
||||
mkdir -p ../../Root/usr/lib
|
||||
# Copy headers
|
||||
rsync -r -a --include '*/' --include '*.h' --exclude '*' . ../../Root/usr/include/LibAudio
|
||||
rsync -r -a --include '*/' --include '*.h' --exclude '*' ../../AK/ ../../Root/usr/include/AK
|
||||
# Install the library
|
||||
cp $(LIBRARY) ../../Root/usr/lib
|
Loading…
Add table
Add a link
Reference in a new issue