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.
|
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
133
fifo.c
@@ -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
24
fifo.h
@@ -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
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"
|
"-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) {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 **
|
||||||
|
|||||||
Reference in New Issue
Block a user