summaryrefslogtreecommitdiff
path: root/audio.c
diff options
context:
space:
mode:
authorMikhail Burakov <mburakov@mailbox.org>2024-05-05 12:51:07 +0200
committerMikhail Burakov <mburakov@mailbox.org>2024-05-19 10:15:07 +0200
commitf593c2fd65cab3275c98c100c6b7d775e24157f9 (patch)
tree11891ecf9584594896ccb64b8cc91dafcdee23b6 /audio.c
parentf5df0d289d1a06b325b70f9e1c2083ce6080d98a (diff)
Initial primitive audio pipeline implementation
Diffstat (limited to 'audio.c')
-rw-r--r--audio.c131
1 files changed, 131 insertions, 0 deletions
diff --git a/audio.c b/audio.c
new file mode 100644
index 0000000..bb5ef23
--- /dev/null
+++ b/audio.c
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2024 Mikhail Burakov. This file is part of receiver.
+ *
+ * receiver is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * receiver is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with receiver. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "audio.h"
+
+#include <alsa/asoundlib.h>
+#include <errno.h>
+#include <stdatomic.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <threads.h>
+
+#include "atomic_queue.h"
+#include "toolbox/utils.h"
+
+struct AudioContext {
+ const char* device;
+ atomic_bool running;
+ struct AtomicQueue queue;
+ thrd_t thread;
+};
+
+static int AudioContextThreadProc(void* arg) {
+ struct AudioContext* context = arg;
+
+ snd_pcm_t* pcm = NULL;
+ int err = snd_pcm_open(&pcm, context->device, SND_PCM_STREAM_PLAYBACK, 0);
+ if (err) {
+ LOG("Failed to open pcm (%s)", snd_strerror(err));
+ atomic_store_explicit(&context->running, 0, memory_order_relaxed);
+ return 0;
+ }
+
+ // TODO(mburakov): Read audio configuration from the server.
+ err = snd_pcm_set_params(pcm, SND_PCM_FORMAT_S16_LE,
+ SND_PCM_ACCESS_RW_INTERLEAVED, 2, 48000, 1, 10000);
+ if (err) {
+ LOG("Failed to set pcm params (%s)", snd_strerror(err));
+ atomic_store_explicit(&context->running, 0, memory_order_relaxed);
+ goto rollback_pcm;
+ }
+
+ while (atomic_load_explicit(&context->running, memory_order_relaxed)) {
+ // TODO(mburakov): Frame size depends on dynamic audio configuration.
+ static const unsigned frame_size = sizeof(int16_t) * 2;
+ uint8_t buffer[480 * frame_size];
+
+ size_t size = AtomicQueueRead(&context->queue, buffer, sizeof(buffer));
+ if (size < sizeof(buffer)) {
+ // LOG("Audio queue underflow!");
+ memset(buffer + size, 0, sizeof(buffer) - size);
+ }
+
+ for (snd_pcm_uframes_t offset = 0; offset < sizeof(buffer) / frame_size;) {
+ snd_pcm_sframes_t nframes =
+ snd_pcm_writei(pcm, buffer + offset * frame_size,
+ sizeof(buffer) / frame_size - offset);
+ if (nframes < 0) {
+ LOG("Failed to write pcm (%s)", snd_strerror((int)nframes));
+ atomic_store_explicit(&context->running, 0, memory_order_relaxed);
+ goto rollback_pcm;
+ }
+ offset += (snd_pcm_uframes_t)nframes;
+ }
+ }
+
+rollback_pcm:
+ snd_pcm_close(pcm);
+ return 0;
+}
+
+struct AudioContext* AudioContextCreate(const char* device) {
+ struct AudioContext* audio_context = malloc(sizeof(struct AudioContext));
+ if (!audio_context) {
+ LOG("Failed to allocate context (%s)", strerror(errno));
+ return NULL;
+ }
+
+ audio_context->device = device;
+ atomic_init(&audio_context->running, 1);
+ if (!AtomicQueueCreate(&audio_context->queue, 4800 * sizeof(int16_t) * 2)) {
+ LOG("Failed to create queue (%s)", strerror(errno));
+ goto rollback_context;
+ }
+
+ if (thrd_create(&audio_context->thread, AudioContextThreadProc,
+ audio_context) != thrd_success) {
+ LOG("Failed to create thread (%s)", strerror(errno));
+ goto rollback_queue;
+ }
+ return audio_context;
+
+rollback_queue:
+ AtomicQueueDestroy(&audio_context->queue);
+rollback_context:
+ free(audio_context);
+ return NULL;
+}
+
+bool AudioContextDecode(struct AudioContext* audio_context, const void* buffer,
+ size_t size) {
+ if (!atomic_load_explicit(&audio_context->running, memory_order_relaxed)) {
+ LOG("Audio thread was stopped early!");
+ return false;
+ }
+ if (AtomicQueueWrite(&audio_context->queue, buffer, size) < size)
+ LOG("Audio queue overflow!");
+ return true;
+}
+
+void AudioContextDestroy(struct AudioContext* audio_context) {
+ atomic_store_explicit(&audio_context->running, 0, memory_order_relaxed);
+ thrd_join(audio_context->thread, NULL);
+ AtomicQueueDestroy(&audio_context->queue);
+ free(audio_context);
+}