LibAudio/AudioServer: Add support for playing real time audio
This commit is contained in:
@@ -40,6 +40,13 @@ bool AudioServer::on_client_packet(int fd, long smo_key)
|
||||
{
|
||||
auto& audio_buffer = m_audio_buffers[fd];
|
||||
|
||||
if (smo_key == 0)
|
||||
{
|
||||
if (audio_buffer.buffer)
|
||||
reset_kernel_buffer();
|
||||
return true;
|
||||
}
|
||||
|
||||
audio_buffer.buffer = static_cast<LibAudio::AudioBuffer*>(smo_map(smo_key));
|
||||
audio_buffer.sample_frames_queued = 0;
|
||||
if (audio_buffer.buffer == nullptr)
|
||||
@@ -53,8 +60,11 @@ bool AudioServer::on_client_packet(int fd, long smo_key)
|
||||
return true;
|
||||
}
|
||||
|
||||
void AudioServer::update()
|
||||
uint64_t AudioServer::update()
|
||||
{
|
||||
// FIXME: get this from the kernel
|
||||
static constexpr uint64_t kernel_buffer_ms = 50;
|
||||
|
||||
uint32_t kernel_buffer_size;
|
||||
if (ioctl(m_audio_device_fd, SND_GET_BUFFERSZ, &kernel_buffer_size) == -1)
|
||||
ASSERT_NOT_REACHED();
|
||||
@@ -72,14 +82,17 @@ void AudioServer::update()
|
||||
const size_t max_sample_frames = (m_samples.capacity() - m_samples.size()) / m_channels;
|
||||
const size_t queued_samples_end = m_samples.size();
|
||||
if (max_sample_frames == 0)
|
||||
return;
|
||||
return kernel_buffer_ms;
|
||||
|
||||
size_t max_sample_frames_to_queue = max_sample_frames;
|
||||
|
||||
bool anyone_playing = false;
|
||||
for (auto& [_, buffer] : m_audio_buffers)
|
||||
{
|
||||
if (buffer.buffer == nullptr)
|
||||
continue;
|
||||
|
||||
const double sample_ratio = buffer.buffer->sample_rate / static_cast<double>(m_sample_rate);
|
||||
const sample_t sample_ratio = buffer.buffer->sample_rate / static_cast<sample_t>(m_sample_rate);
|
||||
|
||||
if (buffer.sample_frames_queued)
|
||||
{
|
||||
@@ -91,13 +104,38 @@ void AudioServer::update()
|
||||
buffer.sample_frames_queued -= buffer_sample_frames_played;
|
||||
}
|
||||
|
||||
if (buffer.buffer->paused)
|
||||
continue;
|
||||
anyone_playing = true;
|
||||
|
||||
const uint32_t buffer_total_sample_frames = ((buffer.buffer->capacity + buffer.buffer->head - buffer.buffer->tail) % buffer.buffer->capacity) / buffer.buffer->channels;
|
||||
const uint32_t buffer_sample_frames_available = (buffer_total_sample_frames - buffer.sample_frames_queued) / sample_ratio;
|
||||
max_sample_frames_to_queue = BAN::Math::min<size_t>(max_sample_frames_to_queue, buffer_sample_frames_available);
|
||||
}
|
||||
|
||||
if (!anyone_playing)
|
||||
return 60'000;
|
||||
|
||||
// FIXME: this works but if any client stops producing audio samples
|
||||
// the whole audio server halts
|
||||
const uint32_t samples_per_10ms = m_sample_rate / 100;
|
||||
if (max_sample_frames_to_queue < samples_per_10ms)
|
||||
return 1;
|
||||
|
||||
for (auto& [_, buffer] : m_audio_buffers)
|
||||
{
|
||||
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 uint32_t buffer_total_sample_frames = ((buffer.buffer->capacity + buffer.buffer->head - buffer.buffer->tail) % buffer.buffer->capacity) / buffer.buffer->channels;
|
||||
const uint32_t buffer_sample_frames_available = (buffer_total_sample_frames - buffer.sample_frames_queued) / sample_ratio;
|
||||
if (buffer_sample_frames_available == 0)
|
||||
continue;
|
||||
|
||||
const size_t sample_frames_to_queue = BAN::Math::min<size_t>(max_sample_frames, buffer_sample_frames_available);
|
||||
if (sample_frames_to_queue == 0)
|
||||
const size_t sample_frames_to_queue = BAN::Math::min<size_t>(max_sample_frames_to_queue, buffer_sample_frames_available);
|
||||
if (sample_frames_to_queue < samples_per_10ms)
|
||||
continue;
|
||||
|
||||
while (m_samples.size() < queued_samples_end + sample_frames_to_queue * m_channels)
|
||||
@@ -113,10 +151,17 @@ void AudioServer::update()
|
||||
m_samples[queued_samples_end + i * m_channels + j] += buffer.buffer->samples[(buffer_tail + buffer_frame * buffer.buffer->channels + j) % buffer.buffer->capacity];
|
||||
}
|
||||
|
||||
buffer.sample_frames_queued += sample_frames_to_queue * sample_ratio;
|
||||
buffer.sample_frames_queued += BAN::Math::min<uint32_t>(
|
||||
buffer_total_sample_frames,
|
||||
BAN::Math::ceil(sample_frames_to_queue * sample_ratio)
|
||||
);
|
||||
}
|
||||
|
||||
send_samples();
|
||||
|
||||
const double play_ms = 1000.0 * m_samples_sent / m_channels / m_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()
|
||||
|
||||
@@ -19,7 +19,7 @@ public:
|
||||
void on_client_disconnect(int fd);
|
||||
bool on_client_packet(int fd, long smo_key);
|
||||
|
||||
void update();
|
||||
uint64_t update();
|
||||
|
||||
private:
|
||||
struct ClientInfo
|
||||
@@ -28,6 +28,8 @@ private:
|
||||
size_t sample_frames_queued { 0 };
|
||||
};
|
||||
|
||||
using sample_t = LibAudio::AudioBuffer::sample_t;
|
||||
|
||||
private:
|
||||
enum class AddOrRemove { Add, Remove };
|
||||
|
||||
@@ -42,7 +44,7 @@ private:
|
||||
|
||||
size_t m_samples_sent { 0 };
|
||||
BAN::Array<uint8_t, 1024> m_send_buffer;
|
||||
BAN::CircularQueue<double, 64 * 1024> m_samples;
|
||||
BAN::CircularQueue<sample_t, 64 * 1024> m_samples;
|
||||
|
||||
BAN::HashMap<int, ClientInfo> m_audio_buffers;
|
||||
};
|
||||
|
||||
@@ -96,8 +96,7 @@ int main()
|
||||
|
||||
dprintln("AudioServer started");
|
||||
|
||||
constexpr uint64_t update_interval_ms = 100;
|
||||
uint64_t next_update_ms = get_current_ms() + update_interval_ms;
|
||||
uint64_t next_update_ms = get_current_ms();
|
||||
|
||||
for (;;)
|
||||
{
|
||||
@@ -113,11 +112,7 @@ int main()
|
||||
}
|
||||
|
||||
const uint64_t current_ms = get_current_ms();
|
||||
if (current_ms >= next_update_ms)
|
||||
{
|
||||
audio_server->update();
|
||||
next_update_ms = current_ms + update_interval_ms;
|
||||
}
|
||||
next_update_ms = current_ms + audio_server->update();
|
||||
|
||||
const uint64_t timeout_ms = next_update_ms - current_ms;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user