Added audio backends, fifo support, see #78

This commit is contained in:
Jarcode
2018-11-17 21:03:02 -08:00
parent 76325cb126
commit 1337253257
6 changed files with 215 additions and 89 deletions

View File

@@ -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. 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 ## 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: 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
View File

@@ -7,65 +7,112 @@
#include <fcntl.h> #include <fcntl.h>
#include <math.h> #include <math.h>
#include <time.h> #include <time.h>
#include <string.h>
#include <errno.h>
#include <poll.h>
#include "fifo.h" #include "fifo.h"
void* input_fifo(void* data) { /* Implementation struct storage */
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 };
fd = open(audio->source, O_RDONLY); typeof(*audio_impls) audio_impls[sizeof(audio_impls) / sizeof(struct audio_impl*)] = {};
flags = fcntl(fd, F_GETFL, 0); size_t audio_impls_idx = 0;
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
while (1) { /* FIFO backend */
bytes = read(fd, buf, sizeof(buf));
if (bytes == -1) { /* if no bytes read, sleep 10ms and zero shared buffer */ static void init(struct audio_data* audio) {
nanosleep (&req, NULL); if (!audio->source) {
t++; audio->source = strdup("/tmp/mpd.fifo");
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;
} }
} 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); float* bl = (float*) audio->audio_out_l;
if (lo < 0) lo = abs(lo) + 1; float* br = (float*) audio->audio_out_r;
if (tempr >= 0) tempr = tempr + lo; size_t fsz = audio->audio_buf_sz;
else tempr = tempr - lo; 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); struct timespec tv_last = {}, tv;
if (lo < 0) lo = abs(lo) + 1; bool measured = false;
if (templ >= 0) templ = templ + lo;
else templ = templ - lo;
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) { if (audio->channels == 2) {
audio->audio_out_l[n] = templ; bl[idx] = buf[q] / (float) 65535;
audio->audio_out_r[n] = tempr; br[idx] = buf[q + 1] / (float) 65535;
} }
n++; 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; return 0;
} }
AUDIO_ATTACH(fifo);

24
fifo.h
View File

@@ -14,4 +14,26 @@ struct audio_data {
pthread_mutex_t mutex; 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
View File

@@ -177,6 +177,7 @@ static const char* help_str =
"-b, --backend specifies a window creation backend to use. By default, the most\n" "-b, --backend specifies a window creation backend to use. By default, the most\n"
" appropriate backend will be used for the underlying windowing\n" " appropriate backend will be used for the underlying windowing\n"
" system.\n" " system.\n"
"-a, --audio=BACKEND specifies an audio input backend to use.\n"
"-V, --version print application version and exit\n" "-V, --version print application version and exit\n"
"\n" "\n"
"The REQUEST argument is evaluated identically to the \'#request\' preprocessor directive\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" "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" "active configuration root (usually ~/.config/glava if present).\n"
"\n" "\n"
"The BACKEND argument may be any of the following strings (for this particular build):\n"
"%s"
"\n"
GLAVA_VERSION_STRING "\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[] = { static struct option p_opts[] = {
{"help", no_argument, 0, 'h'}, {"help", no_argument, 0, 'h'},
{"verbose", no_argument, 0, 'v'}, {"verbose", no_argument, 0, 'v'},
{"desktop", no_argument, 0, 'd'}, {"desktop", no_argument, 0, 'd'},
{"audio", required_argument, 0, 'a'},
{"request", required_argument, 0, 'r'}, {"request", required_argument, 0, 'r'},
{"entry", required_argument, 0, 'e'}, {"entry", required_argument, 0, 'e'},
{"force-mod", required_argument, 0, 'm'}, {"force-mod", required_argument, 0, 'm'},
@@ -226,7 +231,8 @@ int main(int argc, char** argv) {
* user_path = SHADER_USER_PATH, * user_path = SHADER_USER_PATH,
* entry = "rc.glsl", * entry = "rc.glsl",
* force = NULL, * force = NULL,
* backend = NULL; * backend = NULL,
* audio_impl_name = "pulseaudio";
const char* system_shader_paths[] = { user_path, install_path, NULL }; const char* system_shader_paths[] = { user_path, install_path, NULL };
char** requests = malloc(1); char** requests = malloc(1);
@@ -244,18 +250,25 @@ int main(int argc, char** argv) {
case 'e': entry = optarg; break; case 'e': entry = optarg; break;
case 'm': force = optarg; break; case 'm': force = optarg; break;
case 'b': backend = optarg; break; case 'b': backend = optarg; break;
case 'a': audio_impl_name = optarg; break;
case '?': exit(EXIT_FAILURE); break; case '?': exit(EXIT_FAILURE); break;
case 'V': case 'V':
puts(GLAVA_VERSION_STRING); puts(GLAVA_VERSION_STRING);
exit(EXIT_SUCCESS); exit(EXIT_SUCCESS);
break; break;
default: default:
case 'h': case 'h': {
printf(help_str, argc > 0 ? argv[0] : "glava"); 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); exit(EXIT_SUCCESS);
break; break;
} }
} }
}
if (copy_mode) { if (copy_mode) {
copy_cfg(install_path, user_path, verbose); copy_cfg(install_path, user_path, verbose);
@@ -306,13 +319,27 @@ int main(int argc, char** argv) {
.sample_sz = rd->samplesize_request, .sample_sz = rd->samplesize_request,
.modified = false .modified = false
}; };
if (!audio.source) {
get_pulse_default_sink(&audio); struct audio_impl* impl = NULL;
if (verbose) printf("Using default PulseAudio sink: %s\n", audio.source);
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_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]; float lb[rd->bufsize_request], rb[rd->bufsize_request];
while (rd->alive) { while (rd->alive) {

View File

@@ -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_mainloop_api* mainloop_api;
pa_context* pulseaudio_context; 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)" #error "Unsupported float format (requires 32 bit IEEE (little or big endian) floating point support)"
#endif #endif
void* input_pulse(void* data) { static void* entry(void* data) {
struct audio_data* audio = (struct audio_data*) data; struct audio_data* audio = (struct audio_data*) data;
int i, n; int i, n;
size_t ssz = audio->sample_sz; size_t ssz = audio->sample_sz;
@@ -188,3 +190,5 @@ void* input_pulse(void* data) {
return 0; return 0;
} }
AUDIO_ATTACH(pulseaudio);

View File

@@ -98,9 +98,14 @@
default when GLava itself is a desktop window. */ default when GLava itself is a desktop window. */
#request setclickthrough false #request setclickthrough false
/* PulseAudio source. Can be a number or a name of an audio /* Audio source
sink or device to record from. Set to "auto" to use the
default output device. */ 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" #request setsource "auto"
/* Buffer swap interval (vsync), set to '0' to prevent /* Buffer swap interval (vsync), set to '0' to prevent
@@ -189,7 +194,11 @@
Lower sample rates also can make output more choppy, when Lower sample rates also can make output more choppy, when
not using interpolation. It's generally OK to leave this 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 #request setsamplerate 22050
/* ** DEPRECATED ** /* ** DEPRECATED **