Added audio backends, fifo support, see #78
This commit is contained in:
15
README.md
15
README.md
@@ -68,6 +68,21 @@ GLava aims to be compatible with _most_ EWMH compliant window managers. Below is
|
||||
|
||||
Note that some WMs listed without issues have specific overrides when using the `--desktop` flag. See `shaders/env_*.glsl` files for details.
|
||||
|
||||
## Reading from MPD's FIFO output
|
||||
|
||||
Add the following to your `~/.config/mpd.conf`:
|
||||
|
||||
```
|
||||
audio_output {
|
||||
type "fifo"
|
||||
name "glava_fifo"
|
||||
path "/tmp/mpd.fifo"
|
||||
format "22050:16:2"
|
||||
}
|
||||
```
|
||||
|
||||
Note the `22050` sample rate -- this is the reccommended setting for GLava. Restart MPD (if nessecary) and start GLava with `glava --audio=fifo`.
|
||||
|
||||
## Licensing
|
||||
|
||||
GLava is licensed under the terms of the GPLv3, with the exemption of `khrplatform.h`, which is licensed under the terms in its header. GLava includes some (heavily modified) source code that originated from [cava](https://github.com/karlstav/cava), which was initially provided under the MIT license. The source files that originated from cava are the following:
|
||||
|
||||
133
fifo.c
133
fifo.c
@@ -7,65 +7,112 @@
|
||||
#include <fcntl.h>
|
||||
#include <math.h>
|
||||
#include <time.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <poll.h>
|
||||
|
||||
#include "fifo.h"
|
||||
|
||||
void* input_fifo(void* data) {
|
||||
struct audio_data* audio = (struct audio_data *) data;
|
||||
int fd;
|
||||
int n = 0;
|
||||
signed char buf[1024];
|
||||
int tempr, templ, lo;
|
||||
int q, i;
|
||||
int t = 0;
|
||||
int size = 1024;
|
||||
int bytes = 0;
|
||||
int flags;
|
||||
struct timespec req = { .tv_sec = 0, .tv_nsec = 10000000 };
|
||||
/* Implementation struct storage */
|
||||
|
||||
fd = open(audio->source, O_RDONLY);
|
||||
flags = fcntl(fd, F_GETFL, 0);
|
||||
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
||||
typeof(*audio_impls) audio_impls[sizeof(audio_impls) / sizeof(struct audio_impl*)] = {};
|
||||
size_t audio_impls_idx = 0;
|
||||
|
||||
while (1) {
|
||||
bytes = read(fd, buf, sizeof(buf));
|
||||
/* FIFO backend */
|
||||
|
||||
if (bytes == -1) { /* if no bytes read, sleep 10ms and zero shared buffer */
|
||||
nanosleep (&req, NULL);
|
||||
t++;
|
||||
if (t > 10) {
|
||||
for (i = 0; i < 2048; i++)audio->audio_out_l[i] = 0;
|
||||
for (i = 0; i < 2048; i++)audio->audio_out_r[i] = 0;
|
||||
t = 0;
|
||||
static void init(struct audio_data* audio) {
|
||||
if (!audio->source) {
|
||||
audio->source = strdup("/tmp/mpd.fifo");
|
||||
}
|
||||
} else { /* if bytes read, go ahead */
|
||||
t = 0;
|
||||
for (q = 0; q < (size / 4); q++) {
|
||||
}
|
||||
|
||||
tempr = (buf[4 * q + 3] << 2);
|
||||
static void* entry(void* data) {
|
||||
struct audio_data* audio = (struct audio_data *) data;
|
||||
|
||||
lo = (buf[4 * q + 2] >> 6);
|
||||
if (lo < 0) lo = abs(lo) + 1;
|
||||
if (tempr >= 0) tempr = tempr + lo;
|
||||
else tempr = tempr - lo;
|
||||
float* bl = (float*) audio->audio_out_l;
|
||||
float* br = (float*) audio->audio_out_r;
|
||||
size_t fsz = audio->audio_buf_sz;
|
||||
size_t ssz = audio->sample_sz;
|
||||
|
||||
templ = (buf[4 * q + 1] << 2);
|
||||
int fd;
|
||||
int16_t buf[ssz / 2];
|
||||
size_t q;
|
||||
int timeout = 50;
|
||||
|
||||
lo = (buf[4 * q] >> 6);
|
||||
if (lo < 0) lo = abs(lo) + 1;
|
||||
if (templ >= 0) templ = templ + lo;
|
||||
else templ = templ - lo;
|
||||
struct timespec tv_last = {}, tv;
|
||||
bool measured = false;
|
||||
|
||||
if (audio->channels == 1) audio->audio_out_l[n] = (tempr + templ) / 2;
|
||||
if ((fd = open(audio->source, O_RDONLY)) == -1) {
|
||||
fprintf(stderr, "failed to open FIFO audio source \"%s\": %s\n", audio->source, strerror(errno));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
struct pollfd pfd = {
|
||||
.fd = fd,
|
||||
.events = POLLIN
|
||||
};
|
||||
|
||||
size_t buffer_offset = (fsz - (ssz / 4));
|
||||
|
||||
while (true) {
|
||||
|
||||
/* The poll timeout is set to accommodate an approximate UPS, but has little purpose except
|
||||
for effectively setting the rate of empty samples in the event of the FIFO descriptor
|
||||
blocking for long periods of time. */
|
||||
|
||||
switch (poll(&pfd, 1, timeout)) {
|
||||
case -1:
|
||||
fprintf(stderr, "FIFO backend: poll() failed (%s)\n", strerror(errno));
|
||||
exit(EXIT_FAILURE);
|
||||
case 0:
|
||||
pthread_mutex_lock(&audio->mutex);
|
||||
|
||||
memmove(bl, &bl[ssz / 4], buffer_offset * sizeof(float));
|
||||
memmove(br, &br[ssz / 4], buffer_offset * sizeof(float));
|
||||
|
||||
for (q = 0; q < (ssz / 4); ++q) bl[buffer_offset + q] = 0;
|
||||
for (q = 0; q < (ssz / 4); ++q) br[buffer_offset + q] = 0;
|
||||
|
||||
audio->modified = true;
|
||||
|
||||
pthread_mutex_unlock(&audio->mutex);
|
||||
break;
|
||||
default: {
|
||||
read(fd, buf, sizeof(buf));
|
||||
clock_gettime(CLOCK_REALTIME, measured ? &tv : &tv_last);
|
||||
if (measured) {
|
||||
/* Set the timeout slightly higher than the delay between samples to prevent empty writes */
|
||||
timeout = (((tv.tv_sec - tv_last.tv_sec) * 1000) + ((tv.tv_nsec - tv_last.tv_nsec) / 1000000)) + 1;
|
||||
tv_last = tv;
|
||||
} else measured = true;
|
||||
|
||||
pthread_mutex_lock(&audio->mutex);
|
||||
|
||||
memmove(bl, &bl[ssz / 4], buffer_offset * sizeof(float));
|
||||
memmove(br, &br[ssz / 4], buffer_offset * sizeof(float));
|
||||
|
||||
for (size_t n = 0, q = 0; q < (ssz / 2); q += 2) {
|
||||
|
||||
size_t idx = (fsz - (ssz / 4)) + n;
|
||||
|
||||
if (audio->channels == 1) {
|
||||
float sample = ((buf[q] + buf[q + 1]) / 2) / (float) 65535;
|
||||
bl[idx] = sample;
|
||||
br[idx] = sample;
|
||||
}
|
||||
|
||||
/* stereo storing channels in buffer */
|
||||
if (audio->channels == 2) {
|
||||
audio->audio_out_l[n] = templ;
|
||||
audio->audio_out_r[n] = tempr;
|
||||
bl[idx] = buf[q] / (float) 65535;
|
||||
br[idx] = buf[q + 1] / (float) 65535;
|
||||
}
|
||||
|
||||
n++;
|
||||
if (n == 2048 - 1)n = 0;
|
||||
}
|
||||
|
||||
audio->modified = true;
|
||||
|
||||
pthread_mutex_unlock(&audio->mutex);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,3 +125,5 @@ void* input_fifo(void* data) {
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
AUDIO_ATTACH(fifo);
|
||||
|
||||
24
fifo.h
24
fifo.h
@@ -14,4 +14,26 @@ struct audio_data {
|
||||
pthread_mutex_t mutex;
|
||||
};
|
||||
|
||||
void* input_fifo(void* data);
|
||||
struct audio_impl {
|
||||
const char* name;
|
||||
void (*init)(struct audio_data* data);
|
||||
void* (*entry)(void* data);
|
||||
};
|
||||
|
||||
#define AUDIO_FUNC(F) \
|
||||
.F = (typeof(((struct audio_impl*) NULL)->F)) &F
|
||||
|
||||
extern struct audio_impl* audio_impls[4];
|
||||
extern size_t audio_impls_idx;
|
||||
|
||||
static inline void register_audio_impl(struct audio_impl* impl) { audio_impls[audio_impls_idx++] = impl; }
|
||||
|
||||
#define AUDIO_ATTACH(N) \
|
||||
static struct audio_impl N##_var = { \
|
||||
.name = #N, \
|
||||
AUDIO_FUNC(init), \
|
||||
AUDIO_FUNC(entry), \
|
||||
}; \
|
||||
void __attribute__((constructor)) _##N##_construct(void) { \
|
||||
register_audio_impl(&N##_var); \
|
||||
}
|
||||
|
||||
43
glava.c
43
glava.c
@@ -177,6 +177,7 @@ static const char* help_str =
|
||||
"-b, --backend specifies a window creation backend to use. By default, the most\n"
|
||||
" appropriate backend will be used for the underlying windowing\n"
|
||||
" system.\n"
|
||||
"-a, --audio=BACKEND specifies an audio input backend to use.\n"
|
||||
"-V, --version print application version and exit\n"
|
||||
"\n"
|
||||
"The REQUEST argument is evaluated identically to the \'#request\' preprocessor directive\n"
|
||||
@@ -188,13 +189,17 @@ static const char* help_str =
|
||||
"The FILE argument may be any file path. All specified file paths are relative to the\n"
|
||||
"active configuration root (usually ~/.config/glava if present).\n"
|
||||
"\n"
|
||||
"The BACKEND argument may be any of the following strings (for this particular build):\n"
|
||||
"%s"
|
||||
"\n"
|
||||
GLAVA_VERSION_STRING "\n";
|
||||
|
||||
static const char* opt_str = "dhvVe:Cm:b:r:";
|
||||
static const char* opt_str = "dhvVe:Cm:b:r:a:";
|
||||
static struct option p_opts[] = {
|
||||
{"help", no_argument, 0, 'h'},
|
||||
{"verbose", no_argument, 0, 'v'},
|
||||
{"desktop", no_argument, 0, 'd'},
|
||||
{"audio", required_argument, 0, 'a'},
|
||||
{"request", required_argument, 0, 'r'},
|
||||
{"entry", required_argument, 0, 'e'},
|
||||
{"force-mod", required_argument, 0, 'm'},
|
||||
@@ -226,7 +231,8 @@ int main(int argc, char** argv) {
|
||||
* user_path = SHADER_USER_PATH,
|
||||
* entry = "rc.glsl",
|
||||
* force = NULL,
|
||||
* backend = NULL;
|
||||
* backend = NULL,
|
||||
* audio_impl_name = "pulseaudio";
|
||||
const char* system_shader_paths[] = { user_path, install_path, NULL };
|
||||
|
||||
char** requests = malloc(1);
|
||||
@@ -244,18 +250,25 @@ int main(int argc, char** argv) {
|
||||
case 'e': entry = optarg; break;
|
||||
case 'm': force = optarg; break;
|
||||
case 'b': backend = optarg; break;
|
||||
case 'a': audio_impl_name = optarg; break;
|
||||
case '?': exit(EXIT_FAILURE); break;
|
||||
case 'V':
|
||||
puts(GLAVA_VERSION_STRING);
|
||||
exit(EXIT_SUCCESS);
|
||||
break;
|
||||
default:
|
||||
case 'h':
|
||||
printf(help_str, argc > 0 ? argv[0] : "glava");
|
||||
case 'h': {
|
||||
char buf[2048];
|
||||
size_t bsz = 0;
|
||||
for (size_t t = 0; t < audio_impls_idx; ++t)
|
||||
bsz += snprintf(buf + bsz, sizeof(buf) - bsz, "\t\"%s\"%s\n", audio_impls[t]->name,
|
||||
!strcmp(audio_impls[t]->name, audio_impl_name) ? " (default)" : "");
|
||||
printf(help_str, argc > 0 ? argv[0] : "glava", buf);
|
||||
exit(EXIT_SUCCESS);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (copy_mode) {
|
||||
copy_cfg(install_path, user_path, verbose);
|
||||
@@ -306,13 +319,27 @@ int main(int argc, char** argv) {
|
||||
.sample_sz = rd->samplesize_request,
|
||||
.modified = false
|
||||
};
|
||||
if (!audio.source) {
|
||||
get_pulse_default_sink(&audio);
|
||||
if (verbose) printf("Using default PulseAudio sink: %s\n", audio.source);
|
||||
|
||||
struct audio_impl* impl = NULL;
|
||||
|
||||
for (size_t t = 0; t < audio_impls_idx; ++t) {
|
||||
if (!strcmp(audio_impls[t]->name, audio_impl_name)) {
|
||||
impl = audio_impls[t];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!impl) {
|
||||
fprintf(stderr, "The specified audio backend (\"%s\") is not available.\n", audio_impl_name);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
impl->init(&audio);
|
||||
|
||||
if (verbose) printf("Using audio source: %s\n", audio.source);
|
||||
|
||||
pthread_t thread;
|
||||
pthread_create(&thread, NULL, input_pulse, (void*) &audio);
|
||||
pthread_create(&thread, NULL, impl->entry, (void*) &audio);
|
||||
|
||||
float lb[rd->bufsize_request], rb[rd->bufsize_request];
|
||||
while (rd->alive) {
|
||||
|
||||
@@ -54,7 +54,9 @@ static void pulseaudio_context_state_callback(pa_context* pulseaudio_context, vo
|
||||
}
|
||||
|
||||
|
||||
void get_pulse_default_sink(struct audio_data* audio) {
|
||||
static void init(struct audio_data* audio) {
|
||||
|
||||
if (audio->source) return;
|
||||
|
||||
pa_mainloop_api* mainloop_api;
|
||||
pa_context* pulseaudio_context;
|
||||
@@ -106,7 +108,7 @@ void get_pulse_default_sink(struct audio_data* audio) {
|
||||
#error "Unsupported float format (requires 32 bit IEEE (little or big endian) floating point support)"
|
||||
#endif
|
||||
|
||||
void* input_pulse(void* data) {
|
||||
static void* entry(void* data) {
|
||||
struct audio_data* audio = (struct audio_data*) data;
|
||||
int i, n;
|
||||
size_t ssz = audio->sample_sz;
|
||||
@@ -188,3 +190,5 @@ void* input_pulse(void* data) {
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
AUDIO_ATTACH(pulseaudio);
|
||||
|
||||
@@ -98,9 +98,14 @@
|
||||
default when GLava itself is a desktop window. */
|
||||
#request setclickthrough false
|
||||
|
||||
/* PulseAudio source. Can be a number or a name of an audio
|
||||
sink or device to record from. Set to "auto" to use the
|
||||
default output device. */
|
||||
/* Audio source
|
||||
|
||||
When the "pulseaudio" backend is set, this can be a number or
|
||||
a name of an audio sink or device to record from. Set to "auto"
|
||||
to use the default output device.
|
||||
|
||||
When the "fifo" backend is set, "auto" is interpreted as
|
||||
"/tmp/mpd.fifo". Otherwise, a valid path should be provided. */
|
||||
#request setsource "auto"
|
||||
|
||||
/* Buffer swap interval (vsync), set to '0' to prevent
|
||||
@@ -189,7 +194,11 @@
|
||||
|
||||
Lower sample rates also can make output more choppy, when
|
||||
not using interpolation. It's generally OK to leave this
|
||||
value unless you have a strange PulseAudio configuration. */
|
||||
value unless you have a strange PulseAudio configuration.
|
||||
|
||||
This option does nothing when using the "fifo" audio
|
||||
backend. Instead, an ideal rate should be be configured
|
||||
in the application generating the output. */
|
||||
#request setsamplerate 22050
|
||||
|
||||
/* ** DEPRECATED **
|
||||
|
||||
Reference in New Issue
Block a user