/*
* 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);
}