AudioServer: Handle multiple audio devices with multiple pins

This makes audio server configurable during runtime!
This commit is contained in:
Bananymous 2026-01-06 21:55:40 +02:00
parent e7c9be1875
commit b7c40eeb57
6 changed files with 213 additions and 59 deletions

View File

@ -1,6 +1,7 @@
#include <BAN/ScopeGuard.h>
#include <LibAudio/Audio.h>
#include <LibAudio/Protocol.h>
#include <fcntl.h>
#include <stdlib.h>
@ -121,10 +122,15 @@ namespace LibAudio
{
ASSERT(m_server_fd != -1);
const ssize_t nsend = send(m_server_fd, &m_smo_key, sizeof(m_smo_key), 0);
LibAudio::Packet packet {
.type = LibAudio::Packet::RegisterBuffer,
.parameter = static_cast<uint64_t>(m_smo_key),
};
const ssize_t nsend = send(m_server_fd, &packet, sizeof(packet), 0);
if (nsend == -1)
return BAN::Error::from_errno(errno);
ASSERT(nsend == sizeof(m_smo_key));
ASSERT(nsend == sizeof(packet));
return {};
}
@ -137,8 +143,12 @@ namespace LibAudio
return;
m_audio_buffer->paused = paused;
long dummy = 0;
send(m_server_fd, &dummy, sizeof(dummy), 0);
LibAudio::Packet packet {
.type = LibAudio::Packet::Notify,
.parameter = 0,
};
send(m_server_fd, &packet, sizeof(packet), 0);
}
size_t Audio::queue_samples(BAN::Span<const AudioBuffer::sample_t> samples)

View File

@ -9,8 +9,6 @@
namespace LibAudio
{
static constexpr BAN::StringView s_audio_server_socket = "/tmp/audio-server.socket"_sv;
struct AudioBuffer
{
using sample_t = double;

View File

@ -0,0 +1,50 @@
#pragma once
#include <BAN/StringView.h>
namespace LibAudio
{
struct Packet
{
enum : uint8_t
{
Notify, // parameter: ignored
// response: nothing
// server refereshes buffered data
RegisterBuffer, // paramenter: smo key
// response: nothing
// register audio buffer to server
QueryDevices, // parameter: ignored
// response: (uint32_t)
// query the number of devices available
QueryPins, // parameter: sink number
// response: (uint32_t)
// query the number of pins the sink has
GetDevice, // parameter: ignored
// reponse: (uint32_t)
// get the currently active device
SetDevice, // parameter: device number
// reponse: nothing
// set the currently active device
GetPin, // parameter: ignored
// response: nothing
// get the active pin of the current device
SetPin, // parameter: pin number
// response: nothing
// set the active pin of the current device
} type;
uint64_t parameter;
};
static constexpr BAN::StringView s_audio_server_socket = "/tmp/audio-server.socket"_sv;
}

View File

@ -3,15 +3,12 @@
#include <sys/banan-os.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <unistd.h>
AudioServer::AudioServer(int fd)
: m_audio_device_fd(fd)
AudioServer::AudioServer(BAN::Vector<AudioDevice>&& audio_devices)
: m_audio_devices(BAN::move(audio_devices))
{
if (ioctl(m_audio_device_fd, SND_GET_CHANNELS, &m_channels) != 0)
ASSERT_NOT_REACHED();
if (ioctl(m_audio_device_fd, SND_GET_SAMPLE_RATE, &m_sample_rate) != 0)
ASSERT_NOT_REACHED();
}
BAN::ErrorOr<void> AudioServer::on_new_client(int fd)
@ -34,29 +31,82 @@ void AudioServer::on_client_disconnect(int fd)
m_audio_buffers.remove(it);
reset_kernel_buffer();
update();
}
bool AudioServer::on_client_packet(int fd, long smo_key)
bool AudioServer::on_client_packet(int fd, LibAudio::Packet packet)
{
auto& audio_buffer = m_audio_buffers[fd];
if (smo_key == 0)
BAN::Optional<uint32_t> response;
switch (packet.type)
{
if (audio_buffer.buffer)
case LibAudio::Packet::Notify:
if (audio_buffer.buffer == nullptr)
break;
reset_kernel_buffer();
return true;
update();
break;
case LibAudio::Packet::RegisterBuffer:
if (audio_buffer.buffer)
{
dwarnln("Client tried to map second audio buffer??");
return false;
}
audio_buffer.buffer = static_cast<LibAudio::AudioBuffer*>(smo_map(packet.parameter));
audio_buffer.queued_head = audio_buffer.buffer->tail;
if (audio_buffer.buffer == nullptr)
{
dwarnln("Failed to map audio buffer: {}", strerror(errno));
return false;
}
reset_kernel_buffer();
update();
break;
case LibAudio::Packet::QueryDevices:
response = m_audio_devices.size();
break;
case LibAudio::Packet::QueryPins:
response = device().total_pins;
break;
case LibAudio::Packet::GetDevice:
response = m_current_audio_device;
break;
case LibAudio::Packet::SetDevice:
if (packet.parameter >= m_audio_devices.size())
{
dwarnln("Client tried to set device {} while there are only {}", packet.parameter, m_audio_devices.size());
return false;
}
reset_kernel_buffer();
m_current_audio_device = packet.parameter;
update();
break;
case LibAudio::Packet::GetPin:
response = device().current_pin;
break;
case LibAudio::Packet::SetPin:
if (packet.parameter >= device().total_pins)
{
dwarnln("Client tried to set pin {} while the device only has {}", packet.parameter, device().total_pins);
return false;
}
reset_kernel_buffer();
if (uint32_t pin = packet.parameter; ioctl(device().fd, SND_SET_PIN, &pin) != 0)
dwarnln("Failed to set pin {}: {}", packet.parameter, strerror(errno));
else
device().current_pin = packet.parameter;
update();
break;
default:
dwarnln("unknown packet type {}", static_cast<uint8_t>(packet.type));
return false;
}
audio_buffer.buffer = static_cast<LibAudio::AudioBuffer*>(smo_map(smo_key));
audio_buffer.queued_head = audio_buffer.buffer->tail;
if (audio_buffer.buffer == nullptr)
{
dwarnln("Failed to map audio buffer: {}", strerror(errno));
return false;
}
reset_kernel_buffer();
if (response.has_value())
if (send(fd, &response.value(), sizeof(uint32_t), 0) != sizeof(uint32_t))
dwarnln("failed to respond to client :(");
return true;
}
@ -66,23 +116,25 @@ uint64_t AudioServer::update()
// FIXME: get this from the kernel
static constexpr uint64_t kernel_buffer_ms = 50;
const auto& device = m_audio_devices[m_current_audio_device];
uint32_t kernel_buffer_size;
if (ioctl(m_audio_device_fd, SND_GET_BUFFERSZ, &kernel_buffer_size) == -1)
if (ioctl(device.fd, SND_GET_BUFFERSZ, &kernel_buffer_size) == -1)
ASSERT_NOT_REACHED();
const size_t kernel_samples = kernel_buffer_size / sizeof(int16_t);
ASSERT(kernel_samples <= m_samples_sent);
const uint32_t samples_played = m_samples_sent - kernel_samples;
ASSERT(samples_played % m_channels == 0);
ASSERT(samples_played % device.channels == 0);
const uint32_t sample_frames_played = samples_played / m_channels;
const uint32_t sample_frames_played = samples_played / device.channels;
for (uint32_t i = 0; i < samples_played; i++)
m_samples.pop();
m_samples_sent -= samples_played;
const size_t max_sample_frames = (m_samples.capacity() - m_samples.size()) / m_channels;
const size_t max_sample_frames = (m_samples.capacity() - m_samples.size()) / device.channels;
const size_t queued_samples_end = m_samples.size();
if (max_sample_frames == 0)
return kernel_buffer_ms;
@ -97,7 +149,7 @@ uint64_t AudioServer::update()
if (const size_t sample_frames_queued = buffer.sample_frames_queued())
{
const sample_t sample_ratio = buffer.buffer->sample_rate / static_cast<sample_t>(m_sample_rate);
const sample_t sample_ratio = buffer.buffer->sample_rate / static_cast<sample_t>(device.sample_rate);
const uint32_t buffer_sample_frames_played = BAN::Math::min<size_t>(
BAN::Math::ceil(sample_frames_played * sample_ratio),
sample_frames_queued
@ -115,10 +167,10 @@ uint64_t AudioServer::update()
if (!anyone_playing)
return 60'000;
const uint32_t sample_frames_per_10ms = m_sample_rate / 100;
const uint32_t sample_frames_per_10ms = device.sample_rate / 100;
if (max_sample_frames_to_queue < sample_frames_per_10ms)
{
const uint32_t sample_frames_sent = m_samples_sent / m_channels;
const uint32_t sample_frames_sent = m_samples_sent / device.channels;
if (sample_frames_sent >= sample_frames_per_10ms)
return 1;
max_sample_frames_to_queue = sample_frames_per_10ms;
@ -129,7 +181,7 @@ uint64_t AudioServer::update()
if (buffer.buffer == nullptr || buffer.buffer->paused)
continue;
const sample_t sample_ratio = buffer.buffer->sample_rate / static_cast<sample_t>(m_sample_rate);
const sample_t sample_ratio = buffer.buffer->sample_rate / static_cast<sample_t>(device.sample_rate);
const size_t sample_frames_to_queue = BAN::Math::min<size_t>(
BAN::Math::ceil(buffer.sample_frames_available() / sample_ratio),
@ -138,17 +190,17 @@ uint64_t AudioServer::update()
if (sample_frames_to_queue == 0)
continue;
while (m_samples.size() < queued_samples_end + sample_frames_to_queue * m_channels)
while (m_samples.size() < queued_samples_end + sample_frames_to_queue * device.channels)
m_samples.push(0.0);
const size_t min_channels = BAN::Math::min(m_channels, buffer.buffer->channels);
const size_t min_channels = BAN::Math::min(device.channels, buffer.buffer->channels);
const size_t buffer_tail = buffer.queued_head;
for (size_t i = 0; i < sample_frames_to_queue; i++)
{
const size_t buffer_frame = i * sample_ratio;
for (size_t j = 0; j < min_channels; j++)
m_samples[queued_samples_end + i * m_channels + j] += buffer.buffer->samples[(buffer_tail + buffer_frame * buffer.buffer->channels + j) % buffer.buffer->capacity];
m_samples[queued_samples_end + i * device.channels + j] += buffer.buffer->samples[(buffer_tail + buffer_frame * buffer.buffer->channels + j) % buffer.buffer->capacity];
}
const uint32_t buffer_sample_frames_queued = BAN::Math::min<uint32_t>(
@ -160,24 +212,26 @@ uint64_t AudioServer::update()
send_samples();
const double play_ms = 1000.0 * m_samples_sent / m_channels / m_sample_rate;
const double play_ms = 1000.0 * m_samples_sent / device.channels / device.sample_rate;
const uint64_t wake_ms = BAN::Math::max<uint64_t>(play_ms, kernel_buffer_ms) - kernel_buffer_ms;
return wake_ms;
}
void AudioServer::reset_kernel_buffer()
{
const auto& device = m_audio_devices[m_current_audio_device];
uint32_t kernel_buffer_size;
if (ioctl(m_audio_device_fd, SND_RESET_BUFFER, &kernel_buffer_size) != 0)
if (ioctl(device.fd, SND_RESET_BUFFER, &kernel_buffer_size) != 0)
ASSERT_NOT_REACHED();
const size_t kernel_samples = kernel_buffer_size / sizeof(int16_t);
ASSERT(kernel_samples <= m_samples_sent);
const uint32_t samples_played = m_samples_sent - kernel_samples;
ASSERT(samples_played % m_channels == 0);
ASSERT(samples_played % device.channels == 0);
const uint32_t sample_frames_played = samples_played / m_channels;
const uint32_t sample_frames_played = samples_played / device.channels;
m_samples_sent = 0;
m_samples.clear();
@ -189,7 +243,7 @@ void AudioServer::reset_kernel_buffer()
if (const size_t sample_frames_queued = buffer.sample_frames_queued())
{
const sample_t sample_ratio = buffer.buffer->sample_rate / static_cast<sample_t>(m_sample_rate);
const sample_t sample_ratio = buffer.buffer->sample_rate / static_cast<sample_t>(device.sample_rate);
const uint32_t buffer_sample_frames_played = BAN::Math::min<size_t>(
BAN::Math::ceil(sample_frames_played * sample_ratio),
sample_frames_queued
@ -198,8 +252,6 @@ void AudioServer::reset_kernel_buffer()
buffer.queued_head = buffer.buffer->tail;
}
}
update();
}
void AudioServer::send_samples()
@ -232,7 +284,7 @@ void AudioServer::send_samples()
while (nwritten < buffer.size())
{
const ssize_t nwrite = write(
m_audio_device_fd,
device().fd,
buffer.data() + nwritten,
buffer.size() - nwritten
);

View File

@ -6,6 +6,16 @@
#include <BAN/HashMap.h>
#include <LibAudio/Audio.h>
#include <LibAudio/Protocol.h>
struct AudioDevice
{
int fd;
uint32_t channels;
uint32_t sample_rate;
uint32_t total_pins;
uint32_t current_pin;
};
class AudioServer
{
@ -13,14 +23,17 @@ class AudioServer
BAN_NON_COPYABLE(AudioServer);
public:
AudioServer(int audio_device_fd);
AudioServer(BAN::Vector<AudioDevice>&& audio_devices);
BAN::ErrorOr<void> on_new_client(int fd);
void on_client_disconnect(int fd);
bool on_client_packet(int fd, long smo_key);
bool on_client_packet(int fd, LibAudio::Packet);
uint64_t update();
private:
AudioDevice& device() { return m_audio_devices[m_current_audio_device]; }
private:
struct ClientInfo
{
@ -48,9 +61,8 @@ private:
void send_samples();
private:
const int m_audio_device_fd;
uint32_t m_sample_rate;
uint32_t m_channels;
BAN::Vector<AudioDevice> m_audio_devices;
size_t m_current_audio_device { 0 };
size_t m_samples_sent { 0 };
BAN::Array<uint8_t, 4 * 1024> m_send_buffer;

View File

@ -5,6 +5,7 @@
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/stat.h>
@ -55,6 +56,21 @@ static uint64_t get_current_ms()
return ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
}
static BAN::Optional<AudioDevice> initialize_audio_device(int fd)
{
AudioDevice result {};
result.fd = fd;
if (ioctl(fd, SND_GET_CHANNELS, &result.channels) != 0)
return {};
if (ioctl(fd, SND_GET_SAMPLE_RATE, &result.sample_rate) != 0)
return {};
if (ioctl(fd, SND_GET_TOTAL_PINS, &result.total_pins) != 0)
return {};
if (ioctl(fd, SND_GET_PIN, &result.current_pin) != 0)
return {};
return result;
}
int main()
{
constexpr int non_terminating_signals[] {
@ -69,14 +85,30 @@ int main()
for (int sig : non_terminating_signals)
signal(sig, SIG_DFL);
const int audio_device_fd = open("/dev/audio0", O_RDWR | O_NONBLOCK);
if (audio_device_fd == -1)
BAN::Vector<AudioDevice> audio_devices;
for (int i = 0; i < 16; i++)
{
dwarnln("failed to open audio device: {}", strerror(errno));
char path[PATH_MAX];
sprintf(path, "/dev/audio%d", i);
const int fd = open(path, O_RDWR | O_NONBLOCK);
if (fd == -1)
continue;
auto device = initialize_audio_device(fd);
if (!device.has_value())
close(fd);
else
MUST(audio_devices.push_back(device.release_value()));
}
if (audio_devices.empty())
{
dwarnln("could not open any audio device");
return 1;
}
auto* audio_server = new AudioServer(audio_device_fd);
auto* audio_server = new AudioServer(BAN::move(audio_devices));
if (audio_server == nullptr)
{
dwarnln("Failed to allocate AudioServer: {}", strerror(errno));
@ -157,17 +189,17 @@ int main()
if (!FD_ISSET(client.fd, &fds))
continue;
long smo_key;
const ssize_t nrecv = recv(client.fd, &smo_key, sizeof(smo_key), 0);
LibAudio::Packet packet;
const ssize_t nrecv = recv(client.fd, &packet, sizeof(packet), 0);
if (nrecv < static_cast<ssize_t>(sizeof(smo_key)) || !audio_server->on_client_packet(client.fd, smo_key))
if (nrecv < static_cast<ssize_t>(sizeof(packet)) || !audio_server->on_client_packet(client.fd, packet))
{
if (nrecv == 0)
;
else if (nrecv < 0)
dwarnln("recv: {}", strerror(errno));
else if (nrecv < static_cast<ssize_t>(sizeof(smo_key)))
dwarnln("client sent only {} bytes, {} expected", nrecv, sizeof(smo_key));
else if (nrecv < static_cast<ssize_t>(sizeof(packet)))
dwarnln("client sent only {} bytes, {} expected", nrecv, sizeof(packet));
audio_server->on_client_disconnect(client.fd);
close(client.fd);