diff options
Diffstat (limited to 'audio.c')
-rw-r--r-- | audio.c | 131 |
1 files changed, 131 insertions, 0 deletions
@@ -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); +} |