diff --git a/userspace/libraries/LibAudio/Audio.cpp b/userspace/libraries/LibAudio/Audio.cpp index fbea99eb..8d81da73 100644 --- a/userspace/libraries/LibAudio/Audio.cpp +++ b/userspace/libraries/LibAudio/Audio.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include @@ -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(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 samples) diff --git a/userspace/libraries/LibAudio/include/LibAudio/Audio.h b/userspace/libraries/LibAudio/include/LibAudio/Audio.h index d0f1832d..c19286f6 100644 --- a/userspace/libraries/LibAudio/include/LibAudio/Audio.h +++ b/userspace/libraries/LibAudio/include/LibAudio/Audio.h @@ -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; diff --git a/userspace/libraries/LibAudio/include/LibAudio/Protocol.h b/userspace/libraries/LibAudio/include/LibAudio/Protocol.h new file mode 100644 index 00000000..bcfd3a17 --- /dev/null +++ b/userspace/libraries/LibAudio/include/LibAudio/Protocol.h @@ -0,0 +1,50 @@ +#pragma once + +#include + +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; + +} diff --git a/userspace/programs/AudioServer/AudioServer.cpp b/userspace/programs/AudioServer/AudioServer.cpp index ad79123c..2d7fc480 100644 --- a/userspace/programs/AudioServer/AudioServer.cpp +++ b/userspace/programs/AudioServer/AudioServer.cpp @@ -3,15 +3,12 @@ #include #include #include +#include #include -AudioServer::AudioServer(int fd) - : m_audio_device_fd(fd) +AudioServer::AudioServer(BAN::Vector&& 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 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 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(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(packet.type)); + return false; } - audio_buffer.buffer = static_cast(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(m_sample_rate); + const sample_t sample_ratio = buffer.buffer->sample_rate / static_cast(device.sample_rate); const uint32_t buffer_sample_frames_played = BAN::Math::min( 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(m_sample_rate); + const sample_t sample_ratio = buffer.buffer->sample_rate / static_cast(device.sample_rate); const size_t sample_frames_to_queue = BAN::Math::min( 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( @@ -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(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(m_sample_rate); + const sample_t sample_ratio = buffer.buffer->sample_rate / static_cast(device.sample_rate); const uint32_t buffer_sample_frames_played = BAN::Math::min( 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 ); diff --git a/userspace/programs/AudioServer/AudioServer.h b/userspace/programs/AudioServer/AudioServer.h index 70802c35..3cfe18ca 100644 --- a/userspace/programs/AudioServer/AudioServer.h +++ b/userspace/programs/AudioServer/AudioServer.h @@ -6,6 +6,16 @@ #include #include +#include + +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&& audio_devices); BAN::ErrorOr 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 m_audio_devices; + size_t m_current_audio_device { 0 }; size_t m_samples_sent { 0 }; BAN::Array m_send_buffer; diff --git a/userspace/programs/AudioServer/main.cpp b/userspace/programs/AudioServer/main.cpp index 65915087..83e07e89 100644 --- a/userspace/programs/AudioServer/main.cpp +++ b/userspace/programs/AudioServer/main.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -55,6 +56,21 @@ static uint64_t get_current_ms() return ts.tv_sec * 1000 + ts.tv_nsec / 1000000; } +static BAN::Optional 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 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(sizeof(smo_key)) || !audio_server->on_client_packet(client.fd, smo_key)) + if (nrecv < static_cast(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(sizeof(smo_key))) - dwarnln("client sent only {} bytes, {} expected", nrecv, sizeof(smo_key)); + else if (nrecv < static_cast(sizeof(packet))) + dwarnln("client sent only {} bytes, {} expected", nrecv, sizeof(packet)); audio_server->on_client_disconnect(client.fd); close(client.fd);