nirv.git
nirv.c
/* nirv - music player
* Copyright (C) 2023 ArcNyxx
* see LICENCE file for licensing information */
#include <limits.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <alsa/asoundlib.h>
#include <FLAC/stream_decoder.h>
#define SONG "/tmp/nirv-song"
#define INFO "/tmp/nirv-info"
#define ST meta->data.stream_info
#define CM meta->data.vorbis_comment
#define COMMENT(var, name) \
if (!memcmp(CM.comments[i].entry, name, sizeof(name) - 1)) { \
if ((var = strdup((char *) \
CM.comments[i].entry + sizeof(name) - 1)) == NULL) \
DIEN("unable to allocate memory: "); \
}
#define DIEN(fmt) die("nirv: " fmt)
#define DIES(fmt) die("nirv: " fmt ": %s\n", snd_strerror(err))
#define DIEV(fmt, ...) die("nirv: " fmt, __VA_ARGS__)
static volatile sig_atomic_t run = 1;
static uint32_t data[FLAC__MAX_CHANNELS][FLAC__MAX_BLOCK_SIZE],
len, bits, chan, rate;
static char path[PATH_MAX], *song, *album, *artist;
static uint64_t elapsed, duration;
static void die(const char *fmt, ...);
static void term(int signo);
static FLAC__StreamDecoderWriteStatus sound(const FLAC__StreamDecoder *dec,
const FLAC__Frame *frame, const FLAC__int32 *const *buf,
void *data);
static void meta(const FLAC__StreamDecoder *dec,
const FLAC__StreamMetadata *meta, void *data);
static void error(const FLAC__StreamDecoder *dec,
FLAC__StreamDecoderErrorStatus err, void *data);
static void
die(const char *fmt, ...)
{
va_list list;
va_start(list, fmt);
vfprintf(stderr, fmt, list);
va_end(list);
if (fmt[strlen(fmt) - 1] != '\n')
perror(NULL);
exit(1);
}
static void
term(int signo)
{
run = 0;
}
static FLAC__StreamDecoderWriteStatus
sound(const FLAC__StreamDecoder *dec, const FLAC__Frame *frame,
const FLAC__int32 *const *buf, void *null)
{
if (frame->header.bits_per_sample != bits || frame->header.channels !=
chan || frame->header.sample_rate != rate)
DIEN("flac file does not conform to subset format\n");
len = frame->header.blocksize;
for (uint32_t i = 0; i < chan; ++i)
memcpy(data[i], buf[i], len * sizeof(int));
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}
static void
meta(const FLAC__StreamDecoder *dec, const FLAC__StreamMetadata *meta,
void *null)
{
if (meta->type == FLAC__METADATA_TYPE_STREAMINFO) {
duration = ST.total_samples; elapsed = 0; chan = ST.channels;
bits = ST.bits_per_sample; rate = ST.sample_rate;
return;
}
free(song); free(album); free(artist); song = album = artist = NULL;
for (uint32_t i = 0; i < CM.num_comments; ++i)
COMMENT(song, "TITLE=") else COMMENT(artist, "ARTIST=")
else COMMENT(album, "ALBUM=")
}
static void
error(const FLAC__StreamDecoder *dec, FLAC__StreamDecoderErrorStatus err,
void *null)
{
DIEV("flac decoding error: %s\n",
FLAC__StreamDecoderErrorStatusString[err]);
}
int
main(void)
{
struct sigaction act = { .sa_handler = term };
sigfillset(&act.sa_mask);
sigaction(SIGINT, &act, NULL);
sigaction(SIGTERM, &act, NULL);
sigdelset(&act.sa_mask, SIGINT);
sigdelset(&act.sa_mask, SIGTERM);
sigprocmask(SIG_BLOCK, &act.sa_mask, NULL);
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask, SIGRTMIN+1);
sigaddset(&act.sa_mask, SIGRTMIN+2);
int err;
snd_pcm_t *pcm;
if ((err = snd_pcm_open(&pcm, "default", SND_PCM_STREAM_PLAYBACK, 0)))
DIES("unable to open default pcm device");
FLAC__StreamDecoder *dec;
if ((dec = FLAC__stream_decoder_new()) == NULL)
DIEN("unable to allocate new flac decoder\n");
int num;
if ((num = snd_pcm_poll_descriptors_count(pcm)) <= 0)
DIEN("unable to get poll descriptors count\n");
struct pollfd fds[num];
if (snd_pcm_poll_descriptors(pcm, fds, num) != num)
DIEN("unable to get poll descriptors\n");
void *arr[FLAC__MAX_CHANNELS];
for (uint32_t i = 0; i < FLAC__MAX_CHANNELS; ++i)
arr[i] = &data[i];
song:
while (run && (realpath(SONG, path) == NULL || !strcmp(SONG, path)))
sleep(1);
if (!run)
goto quit;
if (!FLAC__stream_decoder_set_metadata_respond(dec,
FLAC__METADATA_TYPE_VORBIS_COMMENT))
DIEN("unable to set flac decoder metadata response\n");
if ((err = FLAC__stream_decoder_init_file(dec, path, sound, meta,
error, NULL)) != FLAC__STREAM_DECODER_INIT_STATUS_OK)
DIEV("unable to initialise flac decoder: %s\n",
FLAC__StreamDecoderInitStatusString[err]);
const uint32_t _bits = bits, _chan = chan, _rate = rate;
if (!FLAC__stream_decoder_process_until_end_of_metadata(dec))
DIEN("unable to process flac metadata\n");
int pause = 0;
if (_bits == bits && _chan == chan && _rate == rate)
goto loop;
static const snd_pcm_format_t fmts[4] = { SND_PCM_FORMAT_S8,
SND_PCM_FORMAT_S16, SND_PCM_FORMAT_S24, SND_PCM_FORMAT_S32 };
snd_pcm_format_t fmt = bits % 8 ? bits == 20 ? SND_PCM_FORMAT_S20 :
SND_PCM_FORMAT_UNKNOWN : fmts[bits / 8 - 1];
if (snd_pcm_state(pcm) == SND_PCM_STATE_RUNNING &&
(err = snd_pcm_drain(pcm)))
DIES("unable to drain pcm device");
snd_pcm_hw_params_t *param;
snd_pcm_hw_params_alloca(¶m);
snd_pcm_hw_params_any(pcm, param);
if ((err = snd_pcm_hw_params_set_access(pcm, param,
SND_PCM_ACCESS_RW_NONINTERLEAVED)))
DIES("unable to set alsa hw param: access format");
if ((err = snd_pcm_hw_params_set_format(pcm, param, fmt)))
DIES("unable to set alsa hw param: bit format");
if ((err = snd_pcm_hw_params_set_channels(pcm, param, chan)))
DIES("unable to set alsa hw param: number of channels");
if ((err = snd_pcm_hw_params_set_rate(pcm, param, rate, 0)))
DIES("unable to set alsa hw param: sample rate");
if ((err = snd_pcm_hw_params_set_buffer_size(pcm, param, 4096)))
DIES("unable to set alsa hw param: buffer size");
if ((err = snd_pcm_hw_params(pcm, param)))
DIES("unable to set alsa hw params");
snd_pcm_sw_params_t *swparam;
snd_pcm_sw_params_alloca(&swparam);
snd_pcm_sw_params_current(pcm, swparam);
if ((err = snd_pcm_sw_params_set_start_threshold(pcm, swparam, 4096)))
DIES("unable to set alsa sw param: start threshold");
if ((err = snd_pcm_sw_params_set_avail_min(pcm, swparam, 256)))
DIES("unable to set alsa sw param: period size");
if ((err = snd_pcm_sw_params_set_period_event(pcm, swparam, true)))
DIES("unable to set alsa sw param: period event trigger");
if ((err = snd_pcm_sw_params(pcm, swparam)))
DIES("unable to set alsa sw params");
loop: ;
siginfo_t sig;
while (sigtimedwait(&act.sa_mask, &sig, &(struct timespec)
{ pause, 0 }) != -1) {
if (sig.si_signo == SIGRTMIN+1) {
if ((pause = !pause)) {
if ((err = snd_pcm_drain(pcm)))
DIES("unable to drain pcm device");
} else {
if ((err = snd_pcm_prepare(pcm)))
DIES("unable to start pcm device");
}
} else if (sig.si_signo == SIGRTMIN+2) {
const int64_t change = sig.si_value.sival_int * rate;
if (sig.si_value.sival_int >= 0) {
if ((elapsed += change) >= duration)
goto done;
} else {
if ((elapsed -= -change) >= duration)
elapsed = 0;
}
if (!FLAC__stream_decoder_reset(dec))
DIEN("unable to reset flac decoder\n");
if (!FLAC__stream_decoder_seek_absolute(dec, elapsed))
DIEN("unable to seek sample in flac file\n");
}
}
if (!run)
goto done;
if (pause)
goto loop;
if (poll(fds, num, 1000) == -1 && errno != EINTR)
DIEN("unable to poll descriptors: ");
unsigned short evt;
if ((err = snd_pcm_poll_descriptors_revents(pcm, fds, num, &evt)))
DIES("unable to merge mangled poll revents");
if (evt & POLLERR && snd_pcm_prepare(pcm))
DIES("pcm device buffer underrun");
if (!(evt & POLLOUT))
goto loop;
if (len != 0) {
if ((err = snd_pcm_avail_update(pcm)) < 0)
DIES("unable to query amount of pcm data to write");
const uint32_t amt = (uint32_t)err < len ? (uint32_t)err : len;
if ((err = snd_pcm_writen(pcm, arr, amt)) < 0) {
switch (err) {
default: break;
case -EPIPE:
if ((err = snd_pcm_prepare(pcm)))
DIES("pcm device buffer underrun");
break;
case -ESTRPIPE:
while ((err = snd_pcm_resume(pcm)) == -EAGAIN);
if (err != 0 || (err = snd_pcm_prepare(pcm)))
DIES("pcm device suspended");
}
if ((err = snd_pcm_writen(pcm, arr, amt)) < 0)
DIES("unable to write pcm data");
}
len -= err;
for (uint32_t i = 0; i < chan; ++i)
memmove(data[i], data[i] + err, len * sizeof(int));
elapsed += err;
}
if (len == 0) {
if (FLAC__stream_decoder_get_state(dec) ==
FLAC__STREAM_DECODER_END_OF_STREAM)
goto done;
if (!FLAC__stream_decoder_process_single(dec))
DIEN("unable to decode flac file\n");
}
int fd;
if ((fd = open(INFO, O_CREAT | O_WRONLY, 0666)) == -1)
DIEN("unable to create or open file: " INFO ": ");
if (dprintf(fd, "%s\n%s\n%s\n%s\n%02ld:%02ld\n%02ld:%02ld\n",
path, song, album, artist,
elapsed / rate / 60, elapsed / rate % 60,
duration / rate / 60, duration / rate % 60) < 0)
DIEN("unable to completely write buffer to file: " INFO ": ");
if (close(fd) == -1)
DIEN("unable to close file: " INFO ": ");
if (run)
goto loop;
done:
FLAC__stream_decoder_finish(dec);
if (unlink(INFO) == -1 && errno != ENOENT)
DIEN("unable to remove file: " INFO ": ");
if (run)
goto song;
quit:
FLAC__stream_decoder_delete(dec);
snd_pcm_close(pcm);
}