From e8e5747650626442f8f9940ad5bc37b21ca85ad9 Mon Sep 17 00:00:00 2001 From: Takamichi Horikawa Date: Mon, 17 Jul 2017 00:38:13 +0900 Subject: soundout autoconf --- common/fmplayer_drumrom_unix.c | 2 +- gtk/Makefile.am | 17 +++-- gtk/configure.ac | 23 +++++- gtk/soundout/alsaout.c | 152 -------------------------------------- gtk/soundout/alsaout.h | 9 --- gtk/soundout/jackout.c | 127 ------------------------------- gtk/soundout/jackout.h | 9 --- gtk/soundout/ossout.c | 8 -- gtk/soundout/pulseout.c | 133 --------------------------------- gtk/soundout/pulseout.h | 9 --- gtk/soundout/soundout.c | 14 ---- gtk/soundout/soundout.h | 16 ---- soundout/alsaout.c | 161 ++++++++++++++++++++++++++++++++++++++++ soundout/alsaout.h | 9 +++ soundout/jackout.c | 127 +++++++++++++++++++++++++++++++ soundout/jackout.h | 9 +++ soundout/ossout.c | 8 ++ soundout/pulseout.c | 164 +++++++++++++++++++++++++++++++++++++++++ soundout/pulseout.h | 9 +++ soundout/soundout.c | 20 +++++ soundout/soundout.h | 16 ++++ 21 files changed, 556 insertions(+), 486 deletions(-) delete mode 100644 gtk/soundout/alsaout.c delete mode 100644 gtk/soundout/alsaout.h delete mode 100644 gtk/soundout/jackout.c delete mode 100644 gtk/soundout/jackout.h delete mode 100644 gtk/soundout/ossout.c delete mode 100644 gtk/soundout/pulseout.c delete mode 100644 gtk/soundout/pulseout.h delete mode 100644 gtk/soundout/soundout.c delete mode 100644 gtk/soundout/soundout.h create mode 100644 soundout/alsaout.c create mode 100644 soundout/alsaout.h create mode 100644 soundout/jackout.c create mode 100644 soundout/jackout.h create mode 100644 soundout/ossout.c create mode 100644 soundout/pulseout.c create mode 100644 soundout/pulseout.h create mode 100644 soundout/soundout.c create mode 100644 soundout/soundout.h diff --git a/common/fmplayer_drumrom_unix.c b/common/fmplayer_drumrom_unix.c index 791b264..9111616 100644 --- a/common/fmplayer_drumrom_unix.c +++ b/common/fmplayer_drumrom_unix.c @@ -11,7 +11,7 @@ static struct { #define DATADIR "/.local/share/fmplayer/" -void loadfile(void) { +static void loadfile(void) { const char *path = "ym2608_adpcm_rom.bin"; const char *home = getenv("HOME"); char *dpath = 0; diff --git a/gtk/Makefile.am b/gtk/Makefile.am index 43a767d..2375b28 100644 --- a/gtk/Makefile.am +++ b/gtk/Makefile.am @@ -19,15 +19,22 @@ FMDSP_SRC=../fmdsp/fmdsp.c \ ../fmdsp/font_fmdsp_small.c \ ../fmdsp/fmdsp_platform_unix.c -SOUNDOUT_SRC=soundout/soundout.c \ - soundout/pulseout.c \ - soundout/jackout.c \ - soundout/alsaout.c +SOUNDOUT_SRC=../soundout/soundout.c + +if ENABLE_JACK +SOUNDOUT_SRC+=../soundout/jackout.c +endif +if ENABLE_PULSE +SOUNDOUT_SRC+=../soundout/pulseout.c +endif +if ENABLE_ALSA +SOUNDOUT_SRC+=../soundout/alsaout.c +endif #fmplayer_CFLAGS=$(CFLAGS) #CFLAGS= fmplayer_CPPFLAGS=-Wall -Wextra -pedantic \ - -I.. -Isoundout \ + -I.. -I../soundout \ $(GTK3_CFLAGS) $(JACK_CFLAGS) $(PULSE_CFLAGS) $(ALSA_CFLAGS) $(SNDFILE_CFLAGS) fmplayer_LDADD=$(GTK3_LIBS) $(JACK_LIBS) $(PULSE_LIBS) $(ALSA_LIBS) $(SNDFILE_LIBS) -lm -lpthread diff --git a/gtk/configure.ac b/gtk/configure.ac index 8a61ac1..fc79c8c 100644 --- a/gtk/configure.ac +++ b/gtk/configure.ac @@ -6,12 +6,29 @@ AC_PROG_RANLIB AM_PROG_AR AM_PROG_AS -PKG_CHECK_MODULES([JACK], [jack soxr]) -PKG_CHECK_MODULES([PULSE], [libpulse]) -PKG_CHECK_MODULES([ALSA], [alsa]) +PKG_CHECK_MODULES([JACK], [jack soxr], [jack_found=yes], [jack_found=no]) +PKG_CHECK_MODULES([PULSE], [libpulse], [pulse_found=yes], [pulse_found=no]) +PKG_CHECK_MODULES([ALSA], [alsa], [alsa_found=yes], [alsa_found=no]) PKG_CHECK_MODULES([GTK3], [gtk+-3.0 cairo]) PKG_CHECK_MODULES([SNDFILE], [sndfile]) +AS_IF([test "x$jack_found" = "xno" -a "x$pulse_found" = "xno" -a "x$alsa_found" = "xno"], [ + AC_MSG_ERROR([No audio output backend found]) +]) + +AM_CONDITIONAL([ENABLE_JACK], [test "x$jack_found" = "xyes"]) +AS_IF([test "x$jack_found" = "xyes"], [ + AC_DEFINE([ENABLE_JACK]) +]) +AM_CONDITIONAL([ENABLE_PULSE], [test "x$pulse_found" = "xyes"]) +AS_IF([test "x$pulse_found" = "xyes"], [ + AC_DEFINE([ENABLE_PULSE]) +]) +AM_CONDITIONAL([ENABLE_ALSA], [test "x$alsa_found" = "xyes"]) +AS_IF([test "x$alsa_found" = "xyes"], [ + AC_DEFINE([ENABLE_ALSA]) +]) + AC_ARG_ENABLE([neon], AS_HELP_STRING([--enable-neon], [Enable NEON optimized functions for SSG sinc filtering and fmdsp palette lookup. Tested on Cortex-A53 (Raspberry PI 3)])) AM_CONDITIONAL([ENABLE_NEON], [test "x$enable_neon" = "xyes"]) AS_IF([test "x$enable_neon" = "xyes"], [ diff --git a/gtk/soundout/alsaout.c b/gtk/soundout/alsaout.c deleted file mode 100644 index 5c5e667..0000000 --- a/gtk/soundout/alsaout.c +++ /dev/null @@ -1,152 +0,0 @@ -#include "alsaout.h" -#include -#include -#include -#include -#include -#include -#include -#include - -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 deleted file mode 100644 index cd79c27..0000000 --- a/gtk/soundout/alsaout.h +++ /dev/null @@ -1,9 +0,0 @@ -#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 deleted file mode 100644 index 54e7f16..0000000 --- a/gtk/soundout/jackout.c +++ /dev/null @@ -1,127 +0,0 @@ -#include "jackout.h" - -#include -#include -#include -#include - -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 deleted file mode 100644 index 4b72f2e..0000000 --- a/gtk/soundout/jackout.h +++ /dev/null @@ -1,9 +0,0 @@ -#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 deleted file mode 100644 index 1083f20..0000000 --- a/gtk/soundout/ossout.c +++ /dev/null @@ -1,8 +0,0 @@ -#include -#include - -struct ossout_state { - struct sound_state ss; -}; - -struct sound_state *ossout_init( diff --git a/gtk/soundout/pulseout.c b/gtk/soundout/pulseout.c deleted file mode 100644 index 9dbcd6c..0000000 --- a/gtk/soundout/pulseout.c +++ /dev/null @@ -1,133 +0,0 @@ -#include "pulseout.h" - -#include -#include -#include -#include - -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 deleted file mode 100644 index d9fbb20..0000000 --- a/gtk/soundout/pulseout.h +++ /dev/null @@ -1,9 +0,0 @@ -#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 deleted file mode 100644 index 56134be..0000000 --- a/gtk/soundout/soundout.c +++ /dev/null @@ -1,14 +0,0 @@ -#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 deleted file mode 100644 index aea926f..0000000 --- a/gtk/soundout/soundout.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef MYON_SOUNDOUT_H_INCLUDED -#define MYON_SOUNDOUT_H_INCLUDED - -#include - -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 diff --git a/soundout/alsaout.c b/soundout/alsaout.c new file mode 100644 index 0000000..0658206 --- /dev/null +++ b/soundout/alsaout.c @@ -0,0 +1,161 @@ +#include "alsaout.h" +#include +#include +#include +#include +#include +#include +#include +#include + +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; + ssize_t t = read(as->fd_event, &eventdata, sizeof(eventdata)); + (void)t; + } + 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; + + as->paused = pause; + if (!pause && flush) { + snd_pcm_drop(as->apcm); + snd_pcm_prepare(as->apcm); + snd_pcm_start(as->apcm); + } else { + snd_pcm_pause(as->apcm, pause); + } + while (atomic_flag_test_and_set_explicit( + &as->cb_flag, memory_order_acquire)); + atomic_flag_clear_explicit(&as->cb_flag, memory_order_release); + uint64_t event = 1; + ssize_t t = write(as->fd_event, &event, sizeof(event)); + (void)t; +} + +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; + ssize_t t = write(as->fd_event, &event, sizeof(event)); + (void)t; + 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->ss; + +err: + alsaout_free(&as->ss); + return 0; +} diff --git a/soundout/alsaout.h b/soundout/alsaout.h new file mode 100644 index 0000000..cd79c27 --- /dev/null +++ b/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/soundout/jackout.c b/soundout/jackout.c new file mode 100644 index 0000000..54e7f16 --- /dev/null +++ b/soundout/jackout.c @@ -0,0 +1,127 @@ +#include "jackout.h" + +#include +#include +#include +#include + +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/soundout/jackout.h b/soundout/jackout.h new file mode 100644 index 0000000..4b72f2e --- /dev/null +++ b/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/soundout/ossout.c b/soundout/ossout.c new file mode 100644 index 0000000..1083f20 --- /dev/null +++ b/soundout/ossout.c @@ -0,0 +1,8 @@ +#include +#include + +struct ossout_state { + struct sound_state ss; +}; + +struct sound_state *ossout_init( diff --git a/soundout/pulseout.c b/soundout/pulseout.c new file mode 100644 index 0000000..5734991 --- /dev/null +++ b/soundout/pulseout.c @@ -0,0 +1,164 @@ +#include "pulseout.h" + +#include +#include +#include +#include + +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; +}; + +#include + +static void pulseout_success_cb(pa_stream *s, int success, void *userdata) { + (void)s; + (void)success; + struct pulseout_state *ps = userdata; + pa_threaded_mainloop_signal(ps->pa_tm, 0); +} + +static void pulseout_pause(struct sound_state *ss, int pause, int flush) { + struct pulseout_state *ps = (struct pulseout_state *)ss; + if (ps->paused != !!pause) { + if (pause) { + pa_threaded_mainloop_lock(ps->pa_tm); + pa_operation *op_cork = pa_stream_cork(ps->pa_s, 1, pulseout_success_cb, ps); + while (pa_operation_get_state(op_cork) == PA_OPERATION_RUNNING) + pa_threaded_mainloop_wait(ps->pa_tm); + pa_operation_unref(op_cork); + } + ps->paused = pause; + if (!pause) { + //if (flush) ps->flush = true; + if (flush) { + pa_operation *op_flush = pa_stream_flush(ps->pa_s, pulseout_success_cb, ps); + if (op_flush) { + while (pa_operation_get_state(op_flush) == PA_OPERATION_RUNNING) + pa_threaded_mainloop_wait(ps->pa_tm); + pa_operation_unref(op_flush); + } else { + fprintf(stderr, "FLUSH ERR\n"); + } + } + pa_operation *op_cork = pa_stream_cork(ps->pa_s, 0, pulseout_success_cb, ps); + if (op_cork) { + while (pa_operation_get_state(op_cork) == PA_OPERATION_RUNNING) + pa_threaded_mainloop_wait(ps->pa_tm); + pa_operation_unref(op_cork); + } else { + fprintf(stderr, "CORK ERR\n"); + } + 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) { + (void)pa_c; + 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/soundout/pulseout.h b/soundout/pulseout.h new file mode 100644 index 0000000..d9fbb20 --- /dev/null +++ b/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/soundout/soundout.c b/soundout/soundout.c new file mode 100644 index 0000000..a749fe4 --- /dev/null +++ b/soundout/soundout.c @@ -0,0 +1,20 @@ +#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; +#ifdef ENABLE_JACK + ss = jackout_init(clientname, srate, cbfunc, userptr); +#endif + if (ss) return ss; +#ifdef ENABLE_PULSE + ss = pulseout_init(clientname, srate, cbfunc, userptr); +#endif + if (ss) return ss; +#ifdef ENABLE_ALSA + ss = alsaout_init(clientname, srate, cbfunc, userptr); +#endif + return ss; +} diff --git a/soundout/soundout.h b/soundout/soundout.h new file mode 100644 index 0000000..aea926f --- /dev/null +++ b/soundout/soundout.h @@ -0,0 +1,16 @@ +#ifndef MYON_SOUNDOUT_H_INCLUDED +#define MYON_SOUNDOUT_H_INCLUDED + +#include + +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 -- cgit v1.2.3