AudioServer: Handle multiple audio devices with multiple pins
This makes audio server configurable during runtime!
This commit is contained in:
parent
e7c9be1875
commit
b7c40eeb57
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue