aboutsummaryrefslogtreecommitdiff
path: root/gtk/soundout
diff options
context:
space:
mode:
authorTakamichi Horikawa <takamichiho@gmail.com>2017-07-12 00:34:35 +0900
committerTakamichi Horikawa <takamichiho@gmail.com>2017-07-12 00:34:35 +0900
commit44c20e41ddd0c394daf5aa45d88baa95334f04fc (patch)
tree9d503654c49585ee580c4033e086e1a2275b933e /gtk/soundout
parentf0c957012f65775976be05d2497a6a30c222b7e6 (diff)
test soundout
Diffstat (limited to 'gtk/soundout')
-rw-r--r--gtk/soundout/alsaout.c152
-rw-r--r--gtk/soundout/alsaout.h9
-rw-r--r--gtk/soundout/jackout.c127
-rw-r--r--gtk/soundout/jackout.h9
-rw-r--r--gtk/soundout/ossout.c8
-rw-r--r--gtk/soundout/pulseout.c133
-rw-r--r--gtk/soundout/pulseout.h9
-rw-r--r--gtk/soundout/soundout.c14
-rw-r--r--gtk/soundout/soundout.h16
9 files changed, 477 insertions, 0 deletions
diff --git a/gtk/soundout/alsaout.c b/gtk/soundout/alsaout.c
new file mode 100644
index 0000000..5c5e667
--- /dev/null
+++ b/gtk/soundout/alsaout.c
@@ -0,0 +1,152 @@
+#include "alsaout.h"
+#include <stdbool.h>
+#include <stdatomic.h>
+#include <stdlib.h>
+#include <sys/eventfd.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <poll.h>
+#include <asoundlib.h>
+
+enum {
+ BUF_FRAMES = 1024,
+};
+
+struct alsaout_state {
+ struct sound_state ss;
+ sound_callback cbfunc;
+ void *userptr;
+ snd_pcm_t *apcm;
+ int fd_event;
+ int fds_space;
+ struct pollfd *fds;
+ bool paused;
+ bool terminate;
+ atomic_flag cb_flag;
+ bool thread_valid;
+ pthread_t alsa_thread;
+ int16_t buf[BUF_FRAMES*2];
+};
+
+static void *alsaout_thread(void *ptr) {
+ struct alsaout_state *as = ptr;
+ for (;;) {
+ as->fds[0] = (struct pollfd) {
+ .fd = as->fd_event,
+ .events = POLLIN,
+ };
+ int fd_cnt = 1;
+ fd_cnt += snd_pcm_poll_descriptors(
+ as->apcm, as->fds + 1, as->fds_space - 1
+ );
+ int err = poll(as->fds, fd_cnt, -1);
+ if (err <= 0) {
+ continue;
+ }
+ if (as->terminate) return 0;
+ if (as->fds[0].revents) {
+ uint64_t eventdata;
+ read(as->fd_event, &eventdata, sizeof(eventdata));
+ }
+ unsigned short event;
+ if (snd_pcm_poll_descriptors_revents(
+ as->apcm, as->fds + 1, fd_cnt - 1, &event) < 0) continue;
+ if (!event) continue;
+ snd_pcm_sframes_t frames = snd_pcm_avail_update(as->apcm);
+ if (frames <= 0) continue;
+ while (frames) {
+ snd_pcm_sframes_t genframes = frames;
+ if (genframes > BUF_FRAMES) genframes = BUF_FRAMES;
+ while (atomic_flag_test_and_set_explicit(
+ &as->cb_flag, memory_order_acquire));
+ if (as->paused) {
+ atomic_flag_clear_explicit(&as->cb_flag, memory_order_release);
+ break;
+ }
+ as->cbfunc(as->userptr, as->buf, genframes);
+ atomic_flag_clear_explicit(&as->cb_flag, memory_order_release);
+ frames -= genframes;
+ if (snd_pcm_state(as->apcm) == SND_PCM_STATE_XRUN) {
+ err = snd_pcm_prepare(as->apcm);
+ }
+ snd_pcm_sframes_t written = snd_pcm_writei(as->apcm, as->buf, genframes);
+ if (written < 0) {
+ snd_pcm_prepare(as->apcm);
+ }
+ }
+ }
+}
+
+static void alsaout_pause(struct sound_state *ss, int pause, int flush) {
+ struct alsaout_state *as = (struct alsaout_state *)ss;
+
+ while (atomic_flag_test_and_set_explicit(
+ &as->cb_flag, memory_order_acquire));
+ as->paused = pause;
+ snd_pcm_pause(as->apcm, pause);
+ atomic_flag_clear_explicit(&as->cb_flag, memory_order_release);
+ uint64_t event = 1;
+ write(as->fd_event, &event, sizeof(event));
+}
+
+static void alsaout_free(struct sound_state *ss) {
+ if (!ss) return;
+ struct alsaout_state *as = (struct alsaout_state *)ss;
+ if (as->thread_valid) {
+ as->terminate = true;
+ uint64_t event = 1;
+ write(as->fd_event, &event, sizeof(event));
+ pthread_join(as->alsa_thread, 0);
+ }
+ if (as->fd_event != -1) {
+ close(as->fd_event);
+ }
+ if (as->fds) free(as->fds);
+ if (as->apcm) {
+ snd_pcm_close(as->apcm);
+ }
+ free(as);
+}
+
+struct sound_state *alsaout_init(
+ const char *clientname, unsigned srate,
+ sound_callback cbfunc, void *userptr) {
+ (void)clientname;
+ struct alsaout_state *as = malloc(sizeof(*as));
+ if (!as) goto err;
+ *as = (struct alsaout_state) {
+ .ss = {
+ .pause = alsaout_pause,
+ .free = alsaout_free,
+ .apiname = "ALSA",
+ },
+ .cbfunc = cbfunc,
+ .userptr = userptr,
+ .fd_event = -1,
+ .cb_flag = ATOMIC_FLAG_INIT,
+ .paused = true,
+ };
+ if (snd_pcm_open(&as->apcm, "default", SND_PCM_STREAM_PLAYBACK, 0)) {
+ goto err;
+ }
+ as->fd_event = eventfd(0, EFD_CLOEXEC);
+ if (as->fd_event < 0) goto err;
+ as->fds_space = snd_pcm_poll_descriptors_count(as->apcm) + 1;
+ as->fds = malloc(sizeof(*as->fds) * as->fds_space);
+ if (!as->fds) goto err;
+ if (snd_pcm_set_params(
+ as->apcm,
+ SND_PCM_FORMAT_S16, SND_PCM_ACCESS_RW_INTERLEAVED, 2, srate, 1,
+ 1000*1000/60)) {
+ goto err;
+ }
+ if (pthread_create(&as->alsa_thread, 0, alsaout_thread, as)) {
+ goto err;
+ }
+ as->thread_valid = true;
+ return as;
+
+err:
+ alsaout_free(&as->ss);
+ return 0;
+}
diff --git a/gtk/soundout/alsaout.h b/gtk/soundout/alsaout.h
new file mode 100644
index 0000000..cd79c27
--- /dev/null
+++ b/gtk/soundout/alsaout.h
@@ -0,0 +1,9 @@
+#ifndef MYON_ALSAOUT_H_INCLUDED
+#define MYON_ALSAOUT_H_INCLUDED
+
+#include "soundout.h"
+
+struct sound_state *alsaout_init(const char *clientname, unsigned srate, sound_callback cbfunc, void *userptr);
+
+#endif // MYON_ALSAOUT_H_INCLUDED
+
diff --git a/gtk/soundout/jackout.c b/gtk/soundout/jackout.c
new file mode 100644
index 0000000..54e7f16
--- /dev/null
+++ b/gtk/soundout/jackout.c
@@ -0,0 +1,127 @@
+#include "jackout.h"
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <jack/jack.h>
+#include <soxr.h>
+
+typedef jack_default_audio_sample_t sample_t;
+
+enum {
+ RFRAMES = 1024,
+ JFRAMES = 2048,
+};
+
+struct jackout_state {
+ struct sound_state ss;
+ sound_callback cbfunc;
+ void *userptr;
+ jack_client_t *jc;
+ jack_port_t *jp[2];
+ soxr_t soxr;
+ bool paused;
+ int16_t rbuf[RFRAMES*2];
+ float jbuf[JFRAMES*2];
+};
+
+static size_t soxr_cb(void *ptr, soxr_in_t *data, size_t nframes) {
+ struct jackout_state *js = ptr;
+ if (nframes > RFRAMES) nframes = RFRAMES;
+ for (size_t i = 0; i < RFRAMES*2; i++) {
+ js->rbuf[i] = 0;
+ }
+ js->cbfunc(js->userptr, js->rbuf, nframes);
+ *data = js->rbuf;
+ return nframes;
+}
+
+static int jack_cb(jack_nframes_t nframes, void *arg) {
+ struct jackout_state *js = arg;
+ sample_t *out[2];
+ for (int i = 0; i < 2; i++) out[i] = jack_port_get_buffer(js->jp[i], nframes);
+ jack_nframes_t outi = 0;
+ while (nframes) {
+ jack_nframes_t pframes = nframes < JFRAMES ? nframes : JFRAMES;
+ pframes = soxr_output(js->soxr, js->jbuf, pframes);
+ for (jack_nframes_t j = 0; j < pframes; j++) {
+ out[0][outi] = js->jbuf[j*2+0];
+ out[1][outi] = js->jbuf[j*2+1];
+ outi++;
+ }
+ nframes -= pframes;
+ }
+ return 0;
+}
+
+static void jackout_pause(struct sound_state *ss, int pause, int flush) {
+ struct jackout_state *js = (struct jackout_state *)ss;
+ // TODO
+ if (js->paused && !pause) {
+ if (flush) {
+ soxr_clear(js->soxr);
+ soxr_set_input_fn(js->soxr, soxr_cb, js, RFRAMES);
+ }
+ jack_activate(js->jc);
+ jack_connect(js->jc, jack_port_name(js->jp[0]), "system:playback_1");
+ jack_connect(js->jc, jack_port_name(js->jp[1]), "system:playback_2");
+ } else if (!js->paused && pause) {
+ jack_deactivate(js->jc);
+ }
+ js->paused = pause;
+}
+
+static void jackout_free(struct sound_state *ss) {
+ struct jackout_state *js = (struct jackout_state *)ss;
+ if (js) {
+ if (js->jc) {
+ for (int i = 0; i < 2; i++) {
+ if (js->jp[i]) jack_port_unregister(js->jc, js->jp[i]);
+ }
+ jack_client_close(js->jc);
+ }
+ if (js->soxr) soxr_delete(js->soxr);
+ free(js);
+ }
+}
+
+struct sound_state *jackout_init(
+ const char *clientname, unsigned srate,
+ sound_callback cbfunc, void *userptr) {
+ struct jackout_state *js = malloc(sizeof(*js));
+ if (!js) goto err;
+ *js = (struct jackout_state){
+ .ss = {
+ .pause = jackout_pause,
+ .free = jackout_free,
+ .apiname = "JACK Audio",
+ },
+ .cbfunc = cbfunc,
+ .userptr = userptr,
+ .paused = true,
+ };
+ js->jc = jack_client_open(clientname, 0, 0);
+ if (!js->jc) goto err;
+ for (int i = 0; i < 2; i++) {
+ js->jp[i] = jack_port_register(
+ js->jc, i ? "right" : "left", JACK_DEFAULT_AUDIO_TYPE,
+ JackPortIsOutput | JackPortIsTerminal, 0);
+ if (!js->jp[i]) goto err;
+ }
+ soxr_io_spec_t iospec = {
+ .itype = SOXR_INT16_I,
+ .otype = SOXR_FLOAT32_I,
+ .scale = 1.0,
+ };
+ js->soxr = soxr_create(
+ srate, jack_get_sample_rate(js->jc),
+ 2, 0, &iospec, 0, 0
+ );
+ if (!js->soxr) goto err;
+ soxr_set_input_fn(js->soxr, soxr_cb, js, RFRAMES);
+ if (jack_set_process_callback(js->jc, jack_cb, js)) goto err;
+
+ return &js->ss;
+err:
+ jackout_free(&js->ss);
+ return 0;
+}
diff --git a/gtk/soundout/jackout.h b/gtk/soundout/jackout.h
new file mode 100644
index 0000000..4b72f2e
--- /dev/null
+++ b/gtk/soundout/jackout.h
@@ -0,0 +1,9 @@
+#ifndef MYON_JACKOUT_H_INCLUDED
+#define MYON_JACKOUT_H_INCLUDED
+
+#include "soundout.h"
+
+struct sound_state *jackout_init(const char *clientname, unsigned srate, sound_callback cbfunc, void *userptr);
+
+#endif // MYON_JACKOUT_H_INCLUDED
+
diff --git a/gtk/soundout/ossout.c b/gtk/soundout/ossout.c
new file mode 100644
index 0000000..1083f20
--- /dev/null
+++ b/gtk/soundout/ossout.c
@@ -0,0 +1,8 @@
+#include <sys/stat.h>
+#include <fcntl.h>
+
+struct ossout_state {
+ struct sound_state ss;
+};
+
+struct sound_state *ossout_init(
diff --git a/gtk/soundout/pulseout.c b/gtk/soundout/pulseout.c
new file mode 100644
index 0000000..9dbcd6c
--- /dev/null
+++ b/gtk/soundout/pulseout.c
@@ -0,0 +1,133 @@
+#include "pulseout.h"
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <pulse/thread-mainloop.h>
+#include <pulse/stream.h>
+
+struct pulseout_state {
+ struct sound_state ss;
+ bool paused;
+ bool flush;
+ unsigned srate;
+ sound_callback cbfunc;
+ void *userptr;
+ pa_threaded_mainloop *pa_tm;
+ pa_context *pa_c;
+ pa_stream *pa_s;
+ bool pa_status_changed;
+};
+
+static void pulseout_pause(struct sound_state *ss, int pause, int flush) {
+ //return;
+ struct pulseout_state *ps = (struct pulseout_state *)ss;
+ if (ps->paused != !!pause) {
+ if (pause) {
+ pa_threaded_mainloop_lock(ps->pa_tm);
+ }
+ ps->paused = pause;
+ if (!pause) {
+ if (flush) ps->flush = true;
+ pa_threaded_mainloop_unlock(ps->pa_tm);
+ }
+ }
+}
+
+static void pulseout_free(struct sound_state *ss) {
+ struct pulseout_state *ps = (struct pulseout_state *)ss;
+ if (ps) {
+ if (ps->pa_tm) {
+ if (ps->paused) pa_threaded_mainloop_unlock(ps->pa_tm);
+ pa_threaded_mainloop_stop(ps->pa_tm);
+ if (ps->pa_s) {
+ pa_stream_disconnect(ps->pa_s);
+ pa_stream_unref(ps->pa_s);
+ }
+ if (ps->pa_c) pa_context_unref(ps->pa_c);
+ pa_threaded_mainloop_free(ps->pa_tm);
+ }
+ free(ps);
+ }
+}
+
+static void pulseout_cb(pa_stream *p, size_t bytes, void *userdata) {
+ struct pulseout_state *ps = userdata;
+ int16_t *buf;
+ pa_stream_begin_write(p, (void **)&buf, &bytes);
+ size_t nframes = bytes / (2 * sizeof(int16_t));
+ if (!ps->paused) {
+ ps->cbfunc(ps->userptr, buf, nframes);
+ } else {
+ for (size_t i = 0; i < nframes; i++) {
+ buf[i*2+0] = 0;
+ buf[i*2+1] = 0;
+ }
+ }
+ pa_seek_mode_t smode =
+ ps->flush ? PA_SEEK_RELATIVE_ON_READ : PA_SEEK_RELATIVE;
+ pa_stream_write(p, buf, nframes * 2 * sizeof(int16_t), 0, 0, smode);
+ ps->flush = false;
+}
+
+static void pa_c_cb(pa_context *pa_c, void *userdata) {
+ struct pulseout_state *ps = userdata;
+ ps->pa_status_changed = true;
+ pa_threaded_mainloop_signal(ps->pa_tm, 0);
+}
+
+struct sound_state *pulseout_init(
+ const char *clientname, unsigned srate,
+ sound_callback cbfunc, void *userptr) {
+ struct pulseout_state *ps = malloc(sizeof(*ps));
+ if (!ps) goto err;
+ *ps = (struct pulseout_state){
+ .ss = {
+ .pause = pulseout_pause,
+ .free = pulseout_free,
+ .apiname = "PulseAudio",
+ },
+ .cbfunc = cbfunc,
+ .userptr = userptr,
+ .paused = false,
+ .srate = srate,
+ };
+ ps->pa_tm = pa_threaded_mainloop_new();
+ if (!ps->pa_tm) goto err;
+ ps->pa_c = pa_context_new(
+ pa_threaded_mainloop_get_api(ps->pa_tm), clientname
+ );
+ if (!ps->pa_c) goto err;
+ if (pa_context_connect(ps->pa_c, 0, 0, 0) < 0) goto err;
+ pa_context_set_state_callback(ps->pa_c, pa_c_cb, ps);
+ if (pa_threaded_mainloop_start(ps->pa_tm) < 0) goto err;
+ pa_threaded_mainloop_lock(ps->pa_tm);
+ ps->paused = true;
+ for (;;) {
+ while (!ps->pa_status_changed) {
+ pa_threaded_mainloop_wait(ps->pa_tm);
+ }
+ ps->pa_status_changed = false;
+ pa_context_state_t state = pa_context_get_state(ps->pa_c);
+ if (state == PA_CONTEXT_CONNECTING ||
+ state == PA_CONTEXT_AUTHORIZING ||
+ state == PA_CONTEXT_SETTING_NAME) continue;
+ else if (state == PA_CONTEXT_READY) break;
+ else goto err;
+ }
+ pa_sample_spec ss = {
+ .format = PA_SAMPLE_S16NE,
+ .rate = ps->srate,
+ .channels = 2,
+ };
+ ps->pa_s = pa_stream_new(
+ ps->pa_c,
+ "stereoout", &ss, 0
+ );
+ if (!ps->pa_s) goto err;
+ pa_stream_set_write_callback(ps->pa_s, pulseout_cb, ps);
+ if (pa_stream_connect_playback(ps->pa_s, 0, 0, 0, 0, 0) < 0) goto err;
+ return &ps->ss;
+err:
+ pulseout_free(&ps->ss);
+ return 0;
+}
diff --git a/gtk/soundout/pulseout.h b/gtk/soundout/pulseout.h
new file mode 100644
index 0000000..d9fbb20
--- /dev/null
+++ b/gtk/soundout/pulseout.h
@@ -0,0 +1,9 @@
+#ifndef MYON_PULSEOUT_H_INCLUDED
+#define MYON_PULSEOUT_H_INCLUDED
+
+#include "soundout.h"
+
+struct sound_state *pulseout_init(const char *clientname, unsigned srate, sound_callback cbfunc, void *userptr);
+
+#endif // MYON_PULSEOUT_H_INCLUDED
+
diff --git a/gtk/soundout/soundout.c b/gtk/soundout/soundout.c
new file mode 100644
index 0000000..56134be
--- /dev/null
+++ b/gtk/soundout/soundout.c
@@ -0,0 +1,14 @@
+#include "soundout.h"
+#include "jackout.h"
+#include "pulseout.h"
+#include "alsaout.h"
+
+struct sound_state *sound_init(const char *clientname, unsigned srate, sound_callback cbfunc, void *userptr) {
+ struct sound_state *ss = 0;
+ //ss = jackout_init(clientname, srate, cbfunc, userptr);
+ if (ss) return ss;
+ //ss = pulseout_init(clientname, srate, cbfunc, userptr);
+ if (ss) return ss;
+ ss = alsaout_init(clientname, srate, cbfunc, userptr);
+ return ss;
+}
diff --git a/gtk/soundout/soundout.h b/gtk/soundout/soundout.h
new file mode 100644
index 0000000..aea926f
--- /dev/null
+++ b/gtk/soundout/soundout.h
@@ -0,0 +1,16 @@
+#ifndef MYON_SOUNDOUT_H_INCLUDED
+#define MYON_SOUNDOUT_H_INCLUDED
+
+#include <stdint.h>
+
+typedef void (*sound_callback)(void *userptr, int16_t *buf, unsigned frames);
+
+struct sound_state {
+ void (*pause)(struct sound_state *state, int pause, int flush);
+ void (*free)(struct sound_state *state);
+ const char *apiname;
+};
+
+struct sound_state *sound_init(const char *clientname, unsigned srate, sound_callback cbfunc, void *userptr);
+
+#endif // MYON_SOUNDOUT_H_INCLUDED