/* * 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 . */ #include "audio.h" #include #include #include #include #include #include #include #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); }