diff options
Diffstat (limited to 'src/modules')
48 files changed, 9324 insertions, 4661 deletions
diff --git a/src/modules/alsa-util.c b/src/modules/alsa-util.c index 40be5311..906de58d 100644 --- a/src/modules/alsa-util.c +++ b/src/modules/alsa-util.c @@ -33,6 +33,7 @@ #include <pulse/xmalloc.h> #include <pulsecore/log.h> +#include <pulsecore/macro.h> #include "alsa-util.h" @@ -42,7 +43,6 @@ struct pa_alsa_fdlist { /* This is a temporary buffer used to avoid lots of mallocs */ struct pollfd *work_fds; - snd_pcm_t *pcm; snd_mixer_t *mixer; pa_mainloop_api *m; @@ -56,11 +56,16 @@ struct pa_alsa_fdlist { }; static void io_cb(pa_mainloop_api*a, pa_io_event* e, PA_GCC_UNUSED int fd, pa_io_event_flags_t events, void *userdata) { - struct pa_alsa_fdlist *fdl = (struct pa_alsa_fdlist*)userdata; + + struct pa_alsa_fdlist *fdl = userdata; int err, i; unsigned short revents; - assert(a && fdl && (fdl->pcm || fdl->mixer) && fdl->fds && fdl->work_fds); + pa_assert(a); + pa_assert(fdl); + pa_assert(fdl->mixer); + pa_assert(fdl->fds); + pa_assert(fdl->work_fds); if (fdl->polled) return; @@ -69,7 +74,7 @@ static void io_cb(pa_mainloop_api*a, pa_io_event* e, PA_GCC_UNUSED int fd, pa_io memcpy(fdl->work_fds, fdl->fds, sizeof(struct pollfd) * fdl->num_fds); - for (i = 0;i < fdl->num_fds;i++) { + for (i = 0;i < fdl->num_fds; i++) { if (e == fdl->ios[i]) { if (events & PA_IO_EVENT_INPUT) fdl->work_fds[i].revents |= POLLIN; @@ -83,63 +88,46 @@ static void io_cb(pa_mainloop_api*a, pa_io_event* e, PA_GCC_UNUSED int fd, pa_io } } - assert(i != fdl->num_fds); - - if (fdl->pcm) - err = snd_pcm_poll_descriptors_revents(fdl->pcm, fdl->work_fds, fdl->num_fds, &revents); - else - err = snd_mixer_poll_descriptors_revents(fdl->mixer, fdl->work_fds, fdl->num_fds, &revents); + pa_assert(i != fdl->num_fds); - if (err < 0) { - pa_log_error("Unable to get poll revent: %s", - snd_strerror(err)); + if ((err = snd_mixer_poll_descriptors_revents(fdl->mixer, fdl->work_fds, fdl->num_fds, &revents)) < 0) { + pa_log_error("Unable to get poll revent: %s", snd_strerror(err)); return; } a->defer_enable(fdl->defer, 1); - if (revents) { - if (fdl->pcm) - fdl->cb(fdl->userdata); - else - snd_mixer_handle_events(fdl->mixer); - } + if (revents) + snd_mixer_handle_events(fdl->mixer); } static void defer_cb(pa_mainloop_api*a, PA_GCC_UNUSED pa_defer_event* e, void *userdata) { - struct pa_alsa_fdlist *fdl = (struct pa_alsa_fdlist*)userdata; + struct pa_alsa_fdlist *fdl = userdata; int num_fds, i, err; struct pollfd *temp; - assert(a && fdl && (fdl->pcm || fdl->mixer)); + pa_assert(a); + pa_assert(fdl); + pa_assert(fdl->mixer); a->defer_enable(fdl->defer, 0); - if (fdl->pcm) - num_fds = snd_pcm_poll_descriptors_count(fdl->pcm); - else - num_fds = snd_mixer_poll_descriptors_count(fdl->mixer); - assert(num_fds > 0); + num_fds = snd_mixer_poll_descriptors_count(fdl->mixer); + pa_assert(num_fds > 0); if (num_fds != fdl->num_fds) { if (fdl->fds) pa_xfree(fdl->fds); if (fdl->work_fds) pa_xfree(fdl->work_fds); - fdl->fds = pa_xmalloc0(sizeof(struct pollfd) * num_fds); - fdl->work_fds = pa_xmalloc(sizeof(struct pollfd) * num_fds); + fdl->fds = pa_xnew0(struct pollfd, num_fds); + fdl->work_fds = pa_xnew(struct pollfd, num_fds); } memset(fdl->work_fds, 0, sizeof(struct pollfd) * num_fds); - if (fdl->pcm) - err = snd_pcm_poll_descriptors(fdl->pcm, fdl->work_fds, num_fds); - else - err = snd_mixer_poll_descriptors(fdl->mixer, fdl->work_fds, num_fds); - - if (err < 0) { - pa_log_error("Unable to get poll descriptors: %s", - snd_strerror(err)); + if ((err = snd_mixer_poll_descriptors(fdl->mixer, fdl->work_fds, num_fds)) < 0) { + pa_log_error("Unable to get poll descriptors: %s", snd_strerror(err)); return; } @@ -149,18 +137,18 @@ static void defer_cb(pa_mainloop_api*a, PA_GCC_UNUSED pa_defer_event* e, void *u return; if (fdl->ios) { - for (i = 0;i < fdl->num_fds;i++) + for (i = 0; i < fdl->num_fds; i++) a->io_free(fdl->ios[i]); + if (num_fds != fdl->num_fds) { pa_xfree(fdl->ios); - fdl->ios = pa_xmalloc(sizeof(pa_io_event*) * num_fds); - assert(fdl->ios); + fdl->ios = NULL; } - } else { - fdl->ios = pa_xmalloc(sizeof(pa_io_event*) * num_fds); - assert(fdl->ios); } + if (!fdl->ios) + fdl->ios = pa_xnew(pa_io_event*, num_fds); + /* Swap pointers */ temp = fdl->work_fds; fdl->work_fds = fdl->fds; @@ -168,47 +156,41 @@ static void defer_cb(pa_mainloop_api*a, PA_GCC_UNUSED pa_defer_event* e, void *u fdl->num_fds = num_fds; - for (i = 0;i < num_fds;i++) { + for (i = 0;i < num_fds;i++) fdl->ios[i] = a->io_new(a, fdl->fds[i].fd, ((fdl->fds[i].events & POLLIN) ? PA_IO_EVENT_INPUT : 0) | ((fdl->fds[i].events & POLLOUT) ? PA_IO_EVENT_OUTPUT : 0), io_cb, fdl); - assert(fdl->ios[i]); - } } struct pa_alsa_fdlist *pa_alsa_fdlist_new(void) { struct pa_alsa_fdlist *fdl; - fdl = pa_xmalloc(sizeof(struct pa_alsa_fdlist)); + fdl = pa_xnew0(struct pa_alsa_fdlist, 1); fdl->num_fds = 0; fdl->fds = NULL; fdl->work_fds = NULL; - - fdl->pcm = NULL; fdl->mixer = NULL; - fdl->m = NULL; fdl->defer = NULL; fdl->ios = NULL; - fdl->polled = 0; return fdl; } void pa_alsa_fdlist_free(struct pa_alsa_fdlist *fdl) { - assert(fdl); + pa_assert(fdl); if (fdl->defer) { - assert(fdl->m); + pa_assert(fdl->m); fdl->m->defer_free(fdl->defer); } if (fdl->ios) { int i; - assert(fdl->m); + pa_assert(fdl->m); for (i = 0;i < fdl->num_fds;i++) fdl->m->io_free(fdl->ios[i]); pa_xfree(fdl->ios); @@ -222,29 +204,15 @@ void pa_alsa_fdlist_free(struct pa_alsa_fdlist *fdl) { pa_xfree(fdl); } -int pa_alsa_fdlist_init_pcm(struct pa_alsa_fdlist *fdl, snd_pcm_t *pcm_handle, pa_mainloop_api* m, void (*cb)(void *userdata), void *userdata) { - assert(fdl && pcm_handle && m && !fdl->m && cb); - - fdl->pcm = pcm_handle; - fdl->m = m; - - fdl->defer = m->defer_new(m, defer_cb, fdl); - assert(fdl->defer); - - fdl->cb = cb; - fdl->userdata = userdata; - - return 0; -} - -int pa_alsa_fdlist_init_mixer(struct pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, pa_mainloop_api* m) { - assert(fdl && mixer_handle && m && !fdl->m); +int pa_alsa_fdlist_set_mixer(struct pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, pa_mainloop_api* m) { + pa_assert(fdl); + pa_assert(mixer_handle); + pa_assert(m); + pa_assert(!fdl->m); fdl->mixer = mixer_handle; fdl->m = m; - fdl->defer = m->defer_new(m, defer_cb, fdl); - assert(fdl->defer); return 0; } @@ -274,8 +242,8 @@ static int set_format(snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hwparams, pa_s int i, ret; - assert(pcm_handle); - assert(f); + pa_assert(pcm_handle); + pa_assert(f); if ((ret = snd_pcm_hw_params_set_format(pcm_handle, hwparams, format_trans[*f])) >= 0) return ret; @@ -308,7 +276,7 @@ try_auto: /* Set the hardware parameters of the given ALSA device. Returns the * selected fragment settings in *period and *period_size */ -int pa_alsa_set_hw_params(snd_pcm_t *pcm_handle, pa_sample_spec *ss, uint32_t *periods, snd_pcm_uframes_t *period_size) { +int pa_alsa_set_hw_params(snd_pcm_t *pcm_handle, pa_sample_spec *ss, uint32_t *periods, snd_pcm_uframes_t *period_size, int *use_mmap) { int ret = -1; snd_pcm_uframes_t buffer_size; unsigned int r = ss->rate; @@ -316,17 +284,32 @@ int pa_alsa_set_hw_params(snd_pcm_t *pcm_handle, pa_sample_spec *ss, uint32_t *p pa_sample_format_t f = ss->format; snd_pcm_hw_params_t *hwparams; - assert(pcm_handle); - assert(ss); - assert(periods); - assert(period_size); + pa_assert(pcm_handle); + pa_assert(ss); + pa_assert(periods); + pa_assert(period_size); + + snd_pcm_hw_params_alloca(&hwparams); buffer_size = *periods * *period_size; - if ((ret = snd_pcm_hw_params_malloc(&hwparams)) < 0 || - (ret = snd_pcm_hw_params_any(pcm_handle, hwparams)) < 0 || - (ret = snd_pcm_hw_params_set_rate_resample(pcm_handle, hwparams, 0)) < 0 || - (ret = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) + if ((ret = snd_pcm_hw_params_any(pcm_handle, hwparams)) < 0 || + (ret = snd_pcm_hw_params_set_rate_resample(pcm_handle, hwparams, 0)) < 0) + goto finish; + + if (use_mmap && *use_mmap) { + if ((ret = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_MMAP_INTERLEAVED)) < 0) { + + /* mmap() didn't work, fall back to interleaved */ + + if ((ret = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) + goto finish; + + if (use_mmap) + *use_mmap = 0; + } + + } else if ((ret = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) goto finish; if ((ret = set_format(pcm_handle, hwparams, &f)) < 0) @@ -346,7 +329,7 @@ int pa_alsa_set_hw_params(snd_pcm_t *pcm_handle, pa_sample_spec *ss, uint32_t *p goto finish; if (ss->rate != r) { - pa_log_warn("device doesn't support %u Hz, changed to %u Hz.", ss->rate, r); + pa_log_warn("Device %s doesn't support %u Hz, changed to %u Hz.", snd_pcm_name(pcm_handle), ss->rate, r); /* If the sample rate deviates too much, we need to resample */ if (r < ss->rate*.95 || r > ss->rate*1.05) @@ -354,12 +337,12 @@ int pa_alsa_set_hw_params(snd_pcm_t *pcm_handle, pa_sample_spec *ss, uint32_t *p } if (ss->channels != c) { - pa_log_warn("device doesn't support %u channels, changed to %u.", ss->channels, c); + pa_log_warn("Device %s doesn't support %u channels, changed to %u.", snd_pcm_name(pcm_handle), ss->channels, c); ss->channels = c; } if (ss->format != f) { - pa_log_warn("device doesn't support sample format %s, changed to %s.", pa_sample_format_to_string(ss->format), pa_sample_format_to_string(f)); + pa_log_warn("Device %s doesn't support sample format %s, changed to %s.", snd_pcm_name(pcm_handle), pa_sample_format_to_string(ss->format), pa_sample_format_to_string(f)); ss->format = f; } @@ -370,24 +353,54 @@ int pa_alsa_set_hw_params(snd_pcm_t *pcm_handle, pa_sample_spec *ss, uint32_t *p (ret = snd_pcm_hw_params_get_period_size(hwparams, period_size, NULL)) < 0) goto finish; - assert(buffer_size > 0); - assert(*period_size > 0); + pa_assert(buffer_size > 0); + pa_assert(*period_size > 0); *periods = buffer_size / *period_size; - assert(*periods > 0); + pa_assert(*periods > 0); ret = 0; finish: - if (hwparams) - snd_pcm_hw_params_free(hwparams); return ret; } +int pa_alsa_set_sw_params(snd_pcm_t *pcm) { + snd_pcm_sw_params_t *swparams; + int err; + + pa_assert(pcm); + + snd_pcm_sw_params_alloca(&swparams); + + if ((err = snd_pcm_sw_params_current(pcm, swparams) < 0)) { + pa_log_warn("Unable to determine current swparams: %s\n", snd_strerror(err)); + return err; + } + + if ((err = snd_pcm_sw_params_set_stop_threshold(pcm, swparams, (snd_pcm_uframes_t) -1)) < 0) { + pa_log_warn("Unable to set stop threshold: %s\n", snd_strerror(err)); + return err; + } + + if ((err = snd_pcm_sw_params_set_start_threshold(pcm, swparams, (snd_pcm_uframes_t) -1)) < 0) { + pa_log_warn("Unable to set start threshold: %s\n", snd_strerror(err)); + return err; + } + + if ((err = snd_pcm_sw_params(pcm, swparams)) < 0) { + pa_log_warn("Unable to set sw params: %s\n", snd_strerror(err)); + return err; + } + + return 0; +} + int pa_alsa_prepare_mixer(snd_mixer_t *mixer, const char *dev) { int err; - assert(mixer && dev); + pa_assert(mixer); + pa_assert(dev); if ((err = snd_mixer_attach(mixer, dev)) < 0) { pa_log_warn("Unable to attach to mixer %s: %s", dev, snd_strerror(err)); @@ -410,10 +423,11 @@ int pa_alsa_prepare_mixer(snd_mixer_t *mixer, const char *dev) { snd_mixer_elem_t *pa_alsa_find_elem(snd_mixer_t *mixer, const char *name, const char *fallback) { snd_mixer_elem_t *elem; snd_mixer_selem_id_t *sid = NULL; + snd_mixer_selem_id_alloca(&sid); - assert(mixer); - assert(name); + pa_assert(mixer); + pa_assert(name); snd_mixer_selem_id_set_name(sid, name); diff --git a/src/modules/alsa-util.h b/src/modules/alsa-util.h index ea6c7e1d..6f1f927e 100644 --- a/src/modules/alsa-util.h +++ b/src/modules/alsa-util.h @@ -32,15 +32,14 @@ #include <pulse/channelmap.h> -struct pa_alsa_fdlist; +typedef struct pa_alsa_fdlist pa_alsa_fdlist; struct pa_alsa_fdlist *pa_alsa_fdlist_new(void); void pa_alsa_fdlist_free(struct pa_alsa_fdlist *fdl); +int pa_alsa_fdlist_set_mixer(struct pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, pa_mainloop_api* m); -int pa_alsa_fdlist_init_pcm(struct pa_alsa_fdlist *fdl, snd_pcm_t *pcm_handle, pa_mainloop_api* m, void (*cb)(void *userdata), void *userdata); -int pa_alsa_fdlist_init_mixer(struct pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, pa_mainloop_api* m); - -int pa_alsa_set_hw_params(snd_pcm_t *pcm_handle, pa_sample_spec *ss, uint32_t *periods, snd_pcm_uframes_t *period_size); +int pa_alsa_set_hw_params(snd_pcm_t *pcm_handle, pa_sample_spec *ss, uint32_t *periods, snd_pcm_uframes_t *period_size, int *use_mmap); +int pa_alsa_set_sw_params(snd_pcm_t *pcm); int pa_alsa_prepare_mixer(snd_mixer_t *mixer, const char *dev); snd_mixer_elem_t *pa_alsa_find_elem(snd_mixer_t *mixer, const char *name, const char *fallback); diff --git a/src/modules/dbus-util.c b/src/modules/dbus-util.c index 48a45174..fc1e91ea 100644 --- a/src/modules/dbus-util.c +++ b/src/modules/dbus-util.c @@ -26,25 +26,25 @@ #include <config.h> #endif -#include <assert.h> -#include <pulsecore/log.h> -#include <pulsecore/props.h> #include <pulse/xmalloc.h> #include <pulse/timeval.h> +#include <pulsecore/log.h> +#include <pulsecore/props.h> #include "dbus-util.h" struct pa_dbus_connection { - int refcount; + PA_REFCNT_DECLARE; + pa_core *core; DBusConnection *connection; const char *property_name; pa_defer_event* dispatch_event; }; -static void dispatch_cb(pa_mainloop_api *ea, pa_defer_event *ev, void *userdata) -{ - DBusConnection *conn = (DBusConnection *) userdata; +static void dispatch_cb(pa_mainloop_api *ea, pa_defer_event *ev, void *userdata) { + DBusConnection *conn = userdata; + if (dbus_connection_dispatch(conn) == DBUS_DISPATCH_COMPLETE) { /* no more data to process, disable the deferred */ ea->defer_enable(ev, 0); @@ -52,14 +52,17 @@ static void dispatch_cb(pa_mainloop_api *ea, pa_defer_event *ev, void *userdata) } /* DBusDispatchStatusFunction callback for the pa mainloop */ -static void dispatch_status(DBusConnection *conn, DBusDispatchStatus status, - void *userdata) -{ - pa_dbus_connection *c = (pa_dbus_connection*) userdata; +static void dispatch_status(DBusConnection *conn, DBusDispatchStatus status, void *userdata) { + pa_dbus_connection *c = userdata; + + pa_assert(c); + switch(status) { + case DBUS_DISPATCH_COMPLETE: c->core->mainloop->defer_enable(c->dispatch_event, 0); break; + case DBUS_DISPATCH_DATA_REMAINS: case DBUS_DISPATCH_NEED_MEMORY: default: @@ -68,11 +71,13 @@ static void dispatch_status(DBusConnection *conn, DBusDispatchStatus status, } } -static pa_io_event_flags_t -get_watch_flags(DBusWatch *watch) -{ - unsigned int flags = dbus_watch_get_flags(watch); - pa_io_event_flags_t events = PA_IO_EVENT_HANGUP | PA_IO_EVENT_ERROR; +static pa_io_event_flags_t get_watch_flags(DBusWatch *watch) { + unsigned int flags; + pa_io_event_flags_t events = 0; + + pa_assert(watch); + + flags = dbus_watch_get_flags(watch); /* no watch flags for disabled watches */ if (!dbus_watch_get_enabled(watch)) @@ -83,21 +88,22 @@ get_watch_flags(DBusWatch *watch) if (flags & DBUS_WATCH_WRITABLE) events |= PA_IO_EVENT_OUTPUT; - return events; + return events | PA_IO_EVENT_HANGUP | PA_IO_EVENT_ERROR; } /* pa_io_event_cb_t IO event handler */ -static void handle_io_event(PA_GCC_UNUSED pa_mainloop_api *ea, pa_io_event *e, - int fd, pa_io_event_flags_t events, void *userdata) -{ +static void handle_io_event(PA_GCC_UNUSED pa_mainloop_api *ea, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) { unsigned int flags = 0; - DBusWatch *watch = (DBusWatch*) userdata; + DBusWatch *watch = userdata; - assert(fd == dbus_watch_get_fd(watch)); +#if HAVE_DBUS_WATCH_GET_UNIX_FD + pa_assert(fd == dbus_watch_get_unix_fd(watch)); +#else + pa_assert(fd == dbus_watch_get_fd(watch)); +#endif if (!dbus_watch_get_enabled(watch)) { - pa_log_warn("Asked to handle disabled watch: %p %i", - (void *) watch, fd); + pa_log_warn("Asked to handle disabled watch: %p %i", (void*) watch, fd); return; } @@ -114,10 +120,8 @@ static void handle_io_event(PA_GCC_UNUSED pa_mainloop_api *ea, pa_io_event *e, } /* pa_time_event_cb_t timer event handler */ -static void handle_time_event(pa_mainloop_api *ea, pa_time_event* e, - const struct timeval *tv, void *userdata) -{ - DBusTimeout *timeout = (DBusTimeout*) userdata; +static void handle_time_event(pa_mainloop_api *ea, pa_time_event* e, const struct timeval *tv, void *userdata) { + DBusTimeout *timeout = userdata; if (dbus_timeout_get_enabled(timeout)) { struct timeval next = *tv; @@ -130,218 +134,195 @@ static void handle_time_event(pa_mainloop_api *ea, pa_time_event* e, } /* DBusAddWatchFunction callback for pa mainloop */ -static dbus_bool_t add_watch(DBusWatch *watch, void *data) -{ +static dbus_bool_t add_watch(DBusWatch *watch, void *data) { + pa_core *c = PA_CORE(data); pa_io_event *ev; - pa_core *c = (pa_core*) data; - ev = c->mainloop->io_new(c->mainloop, dbus_watch_get_fd(watch), - get_watch_flags(watch), - handle_io_event, (void*) watch); - if (NULL == ev) - return FALSE; + pa_assert(watch); + pa_assert(c); - /* dbus_watch_set_data(watch, (void*) ev, c->mainloop->io_free); */ - dbus_watch_set_data(watch, (void*) ev, NULL); + ev = c->mainloop->io_new( + c->mainloop, +#if HAVE_DBUS_WATCH_GET_UNIX_FD + dbus_watch_get_unix_fd(watch), +#else + dbus_watch_get_fd(watch), +#endif + get_watch_flags(watch), handle_io_event, watch); + + dbus_watch_set_data(watch, ev, NULL); return TRUE; } /* DBusRemoveWatchFunction callback for pa mainloop */ -static void remove_watch(DBusWatch *watch, void *data) -{ - pa_core *c = (pa_core*) data; - pa_io_event *ev = (pa_io_event*) dbus_watch_get_data(watch); +static void remove_watch(DBusWatch *watch, void *data) { + pa_core *c = PA_CORE(data); + pa_io_event *ev; - /* free the event */ - if (NULL != ev) + pa_assert(watch); + pa_assert(c); + + if ((ev = dbus_watch_get_data(watch))) c->mainloop->io_free(ev); } /* DBusWatchToggledFunction callback for pa mainloop */ -static void toggle_watch(DBusWatch *watch, void *data) -{ - pa_core *c = (pa_core*) data; - pa_io_event *ev = (pa_io_event*) dbus_watch_get_data(watch); +static void toggle_watch(DBusWatch *watch, void *data) { + pa_core *c = PA_CORE(data); + pa_io_event *ev; + + pa_assert(watch); + pa_core_assert_ref(c); + + pa_assert_se(ev = dbus_watch_get_data(watch)); /* get_watch_flags() checks if the watch is enabled */ c->mainloop->io_enable(ev, get_watch_flags(watch)); } /* DBusAddTimeoutFunction callback for pa mainloop */ -static dbus_bool_t add_timeout(DBusTimeout *timeout, void *data) -{ - struct timeval tv; +static dbus_bool_t add_timeout(DBusTimeout *timeout, void *data) { + pa_core *c = PA_CORE(data); pa_time_event *ev; - pa_core *c = (pa_core*) data; + struct timeval tv; + + pa_assert(timeout); + pa_assert(c); if (!dbus_timeout_get_enabled(timeout)) return FALSE; - if (!pa_gettimeofday(&tv)) - return -1; - + pa_gettimeofday(&tv); pa_timeval_add(&tv, dbus_timeout_get_interval(timeout) * 1000); - ev = c->mainloop->time_new(c->mainloop, &tv, handle_time_event, - (void*) timeout); - if (NULL == ev) - return FALSE; + ev = c->mainloop->time_new(c->mainloop, &tv, handle_time_event, timeout); - /* dbus_timeout_set_data(timeout, (void*) ev, c->mainloop->time_free); */ - dbus_timeout_set_data(timeout, (void*) ev, NULL); + dbus_timeout_set_data(timeout, ev, NULL); return TRUE; } /* DBusRemoveTimeoutFunction callback for pa mainloop */ -static void remove_timeout(DBusTimeout *timeout, void *data) -{ - pa_core *c = (pa_core*) data; - pa_time_event *ev = (pa_time_event*) dbus_timeout_get_data(timeout); +static void remove_timeout(DBusTimeout *timeout, void *data) { + pa_core *c = PA_CORE(data); + pa_time_event *ev; + + pa_assert(timeout); + pa_assert(c); - /* free the event */ - if (NULL != ev) + if ((ev = dbus_timeout_get_data(timeout))) c->mainloop->time_free(ev); } /* DBusTimeoutToggledFunction callback for pa mainloop */ -static void toggle_timeout(DBusTimeout *timeout, void *data) -{ - struct timeval tv; - pa_core *c = (pa_core*) data; - pa_time_event *ev = (pa_time_event*) dbus_timeout_get_data(timeout); +static void toggle_timeout(DBusTimeout *timeout, void *data) { + pa_core *c = PA_CORE(data); + pa_time_event *ev; + + pa_assert(timeout); + pa_assert(c); + + pa_assert_se(ev = dbus_timeout_get_data(timeout)); if (dbus_timeout_get_enabled(timeout)) { + struct timeval tv; + pa_gettimeofday(&tv); pa_timeval_add(&tv, dbus_timeout_get_interval(timeout) * 1000); + c->mainloop->time_restart(ev, &tv); - } else { - /* disable the timeout */ + } else c->mainloop->time_restart(ev, NULL); - } } -static void -pa_dbus_connection_free(pa_dbus_connection *c) -{ - assert(c); - assert(!dbus_connection_get_is_connected(c->connection)); +static void wakeup_main(void *userdata) { + pa_dbus_connection *c = userdata; - /* already disconnected, just free */ - pa_property_remove(c->core, c->property_name); - c->core->mainloop->defer_free(c->dispatch_event); - dbus_connection_unref(c->connection); - pa_xfree(c); -} + pa_assert(c); -static void -wakeup_main(void *userdata) -{ - pa_dbus_connection *c = (pa_dbus_connection*) userdata; /* this will wakeup the mainloop and dispatch events, although * it may not be the cleanest way of accomplishing it */ c->core->mainloop->defer_enable(c->dispatch_event, 1); } -static pa_dbus_connection* pa_dbus_connection_new(pa_core* c, DBusConnection *conn, const char* name) -{ - pa_dbus_connection *pconn = pa_xnew(pa_dbus_connection, 1); +static pa_dbus_connection* pa_dbus_connection_new(pa_core* c, DBusConnection *conn, const char* name) { + pa_dbus_connection *pconn; - pconn->refcount = 1; + pconn = pa_xnew(pa_dbus_connection, 1); + PA_REFCNT_INIT(pconn); pconn->core = c; pconn->property_name = name; pconn->connection = conn; - pconn->dispatch_event = c->mainloop->defer_new(c->mainloop, dispatch_cb, - (void*) conn); + pconn->dispatch_event = c->mainloop->defer_new(c->mainloop, dispatch_cb, conn); pa_property_set(c, name, pconn); return pconn; } -DBusConnection* pa_dbus_connection_get(pa_dbus_connection *c) -{ - assert(c && c->connection); +DBusConnection* pa_dbus_connection_get(pa_dbus_connection *c){ + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) > 0); + pa_assert(c->connection); + return c->connection; } -void pa_dbus_connection_unref(pa_dbus_connection *c) -{ - assert(c); +void pa_dbus_connection_unref(pa_dbus_connection *c) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) > 0); - /* non-zero refcount, still outstanding refs */ - if (--(c->refcount)) + if (PA_REFCNT_DEC(c) > 0) return; - /* refcount is zero */ if (dbus_connection_get_is_connected(c->connection)) { - /* disconnect as we have no more internal references */ dbus_connection_close(c->connection); - /* must process remaining messages, bit of a kludge to - * handle both unload and shutdown */ - while(dbus_connection_read_write_dispatch(c->connection, -1)); + /* must process remaining messages, bit of a kludge to handle + * both unload and shutdown */ + while (dbus_connection_read_write_dispatch(c->connection, -1)); } - pa_dbus_connection_free(c); + + /* already disconnected, just free */ + pa_property_remove(c->core, c->property_name); + c->core->mainloop->defer_free(c->dispatch_event); + dbus_connection_unref(c->connection); + pa_xfree(c); } -pa_dbus_connection* pa_dbus_connection_ref(pa_dbus_connection *c) -{ - assert(c); +pa_dbus_connection* pa_dbus_connection_ref(pa_dbus_connection *c) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) > 0); - ++(c->refcount); + PA_REFCNT_INC(c); return c; } -pa_dbus_connection* pa_dbus_bus_get(pa_core *c, DBusBusType type, - DBusError *error) -{ - const char* name; +pa_dbus_connection* pa_dbus_bus_get(pa_core *c, DBusBusType type, DBusError *error) { + + static const char *const prop_name[] = { + [DBUS_BUS_SESSION] = "dbus-connection-session", + [DBUS_BUS_SYSTEM] = "dbus-connection-system", + [DBUS_BUS_STARTER] = "dbus-connection-starter" + }; DBusConnection *conn; pa_dbus_connection *pconn; - switch (type) { - case DBUS_BUS_SYSTEM: - name = "dbus-connection-system"; - break; - case DBUS_BUS_SESSION: - name = "dbus-connection-session"; - break; - case DBUS_BUS_STARTER: - name = "dbus-connection-starter"; - break; - default: - assert(0); /* never reached */ - break; - } + pa_assert(type == DBUS_BUS_SYSTEM || type == DBUS_BUS_SESSION || type == DBUS_BUS_STARTER); - if ((pconn = pa_property_get(c, name))) + if ((pconn = pa_property_get(c, prop_name[type]))) return pa_dbus_connection_ref(pconn); - /* else */ - conn = dbus_bus_get_private(type, error); - if (conn == NULL || dbus_error_is_set(error)) { + if (!(conn = dbus_bus_get_private(type, error))) return NULL; - } - pconn = pa_dbus_connection_new(c, conn, name); + pconn = pa_dbus_connection_new(c, conn, prop_name[type]); - /* don't exit on disconnect */ dbus_connection_set_exit_on_disconnect(conn, FALSE); - /* set up the DBUS call backs */ - dbus_connection_set_dispatch_status_function(conn, dispatch_status, - (void*) pconn, NULL); - dbus_connection_set_watch_functions(conn, - add_watch, - remove_watch, - toggle_watch, - (void*) c, NULL); - dbus_connection_set_timeout_functions(conn, - add_timeout, - remove_timeout, - toggle_timeout, - (void*) c, NULL); + dbus_connection_set_dispatch_status_function(conn, dispatch_status, pconn, NULL); + dbus_connection_set_watch_functions(conn, add_watch, remove_watch, toggle_watch, c, NULL); + dbus_connection_set_timeout_functions(conn, add_timeout, remove_timeout, toggle_timeout, c, NULL); dbus_connection_set_wakeup_main_function(conn, wakeup_main, pconn, NULL); return pconn; diff --git a/src/modules/gconf/gconf-helper.c b/src/modules/gconf/gconf-helper.c index 3483b845..abd13287 100644 --- a/src/modules/gconf/gconf-helper.c +++ b/src/modules/gconf/gconf-helper.c @@ -32,6 +32,8 @@ #include <gconf/gconf-client.h> #include <glib.h> +#include <pulsecore/core-util.h> + #define PA_GCONF_ROOT "/system/pulseaudio" #define PA_GCONF_PATH_MODULES PA_GCONF_ROOT"/modules" @@ -40,13 +42,13 @@ static void handle_module(GConfClient *client, const char *name) { gboolean enabled, locked; int i; - snprintf(p, sizeof(p), PA_GCONF_PATH_MODULES"/%s/locked", name); + pa_snprintf(p, sizeof(p), PA_GCONF_PATH_MODULES"/%s/locked", name); locked = gconf_client_get_bool(client, p, FALSE); if (locked) return; - snprintf(p, sizeof(p), PA_GCONF_PATH_MODULES"/%s/enabled", name); + pa_snprintf(p, sizeof(p), PA_GCONF_PATH_MODULES"/%s/enabled", name); enabled = gconf_client_get_bool(client, p, FALSE); printf("%c%s%c", enabled ? '+' : '-', name, 0); @@ -56,11 +58,11 @@ static void handle_module(GConfClient *client, const char *name) { for (i = 0; i < 10; i++) { gchar *n, *a; - snprintf(p, sizeof(p), PA_GCONF_PATH_MODULES"/%s/name%i", name, i); + pa_snprintf(p, sizeof(p), PA_GCONF_PATH_MODULES"/%s/name%i", name, i); if (!(n = gconf_client_get_string(client, p, NULL)) || !*n) break; - snprintf(p, sizeof(p), PA_GCONF_PATH_MODULES"/%s/args%i", name, i); + pa_snprintf(p, sizeof(p), PA_GCONF_PATH_MODULES"/%s/args%i", name, i); a = gconf_client_get_string(client, p, NULL); printf("%s%c%s%c", n, 0, a ? a : "", 0); diff --git a/src/modules/gconf/module-gconf.c b/src/modules/gconf/module-gconf.c index cbe17d20..1c8866de 100644 --- a/src/modules/gconf/module-gconf.c +++ b/src/modules/gconf/module-gconf.c @@ -25,7 +25,6 @@ #include <config.h> #endif -#include <assert.h> #include <string.h> #include <unistd.h> #include <stdlib.h> @@ -34,6 +33,7 @@ #include <sys/types.h> #include <sys/wait.h> #include <fcntl.h> +#include <dirent.h> #ifdef HAVE_SYS_PRCTL_H #include <sys/prctl.h> @@ -95,7 +95,7 @@ struct userdata { static int fill_buf(struct userdata *u) { ssize_t r; - assert(u); + pa_assert(u); if (u->buf_fill >= BUF_MAX) { pa_log("read buffer overflow"); @@ -111,21 +111,21 @@ static int fill_buf(struct userdata *u) { static int read_byte(struct userdata *u) { int ret; - assert(u); + pa_assert(u); if (u->buf_fill < 1) if (fill_buf(u) < 0) return -1; ret = u->buf[0]; - assert(u->buf_fill > 0); + pa_assert(u->buf_fill > 0); u->buf_fill--; memmove(u->buf, u->buf+1, u->buf_fill); return ret; } static char *read_string(struct userdata *u) { - assert(u); + pa_assert(u); for (;;) { char *e; @@ -143,9 +143,9 @@ static char *read_string(struct userdata *u) { } static void unload_one_module(struct userdata *u, struct module_info*m, unsigned i) { - assert(u); - assert(m); - assert(i < m->n_items); + pa_assert(u); + pa_assert(m); + pa_assert(i < m->n_items); if (m->items[i].index == PA_INVALID_INDEX) return; @@ -161,8 +161,8 @@ static void unload_one_module(struct userdata *u, struct module_info*m, unsigned static void unload_all_modules(struct userdata *u, struct module_info*m) { unsigned i; - assert(u); - assert(m); + pa_assert(u); + pa_assert(m); for (i = 0; i < m->n_items; i++) unload_one_module(u, m, i); @@ -180,10 +180,10 @@ static void load_module( pa_module *mod; - assert(u); - assert(m); - assert(name); - assert(args); + pa_assert(u); + pa_assert(m); + pa_assert(name); + pa_assert(args); if (!is_new) { if (m->items[i].index != PA_INVALID_INDEX && @@ -212,8 +212,8 @@ static void module_info_free(void *p, void *userdata) { struct module_info *m = p; struct userdata *u = userdata; - assert(m); - assert(u); + pa_assert(m); + pa_assert(u); unload_all_modules(u, m); pa_xfree(m->name); @@ -356,8 +356,10 @@ static int start_client(const char *n, pid_t *pid) { return pipe_fds[0]; } else { +#ifdef __linux__ + DIR* d; +#endif int max_fd, i; - /* child */ close(pipe_fds[0]); @@ -372,18 +374,48 @@ static int start_client(const char *n, pid_t *pid) { close(2); open("/dev/null", O_WRONLY); - max_fd = 1024; +#ifdef __linux__ + + if ((d = opendir("/proc/self/fd/"))) { + + struct dirent *de; + + while ((de = readdir(d))) { + char *e = NULL; + int fd; + + if (de->d_name[0] == '.') + continue; + + errno = 0; + fd = strtol(de->d_name, &e, 10); + pa_assert(errno == 0 && e && *e == 0); + + if (fd >= 3 && dirfd(d) != fd) + close(fd); + } + + closedir(d); + } else { + +#endif + + max_fd = 1024; #ifdef HAVE_SYS_RESOURCE_H - { - struct rlimit r; - if (getrlimit(RLIMIT_NOFILE, &r) == 0) - max_fd = r.rlim_max; - } + { + struct rlimit r; + if (getrlimit(RLIMIT_NOFILE, &r) == 0) + max_fd = r.rlim_max; + } #endif - for (i = 3; i < max_fd; i++) - close(i); + for (i = 3; i < max_fd; i++) + close(i); +# +#ifdef __linux__ + } +#endif #ifdef PR_SET_PDEATHSIG /* On Linux we can use PR_SET_PDEATHSIG to have the helper @@ -413,12 +445,12 @@ fail: return -1; } -int pa__init(pa_core *c, pa_module*m) { +int pa__init(pa_module*m) { struct userdata *u; int r; u = pa_xnew(struct userdata, 1); - u->core = c; + u->core = m->core; u->module = m; m->userdata = u; u->module_infos = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); @@ -431,8 +463,8 @@ int pa__init(pa_core *c, pa_module*m) { if ((u->fd = start_client(PA_GCONF_HELPER, &u->pid)) < 0) goto fail; - u->io_event = c->mainloop->io_new( - c->mainloop, + u->io_event = m->core->mainloop->io_new( + m->core->mainloop, u->fd, PA_IO_EVENT_INPUT, io_event_cb, @@ -449,21 +481,20 @@ int pa__init(pa_core *c, pa_module*m) { return 0; fail: - pa__done(c, m); + pa__done(m); return -1; } -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module*m) { struct userdata *u; - assert(c); - assert(m); + pa_assert(m); if (!(u = m->userdata)) return; if (u->io_event) - c->mainloop->io_free(u->io_event); + m->core->mainloop->io_free(u->io_event); if (u->fd >= 0) close(u->fd); diff --git a/src/modules/ladspa.h b/src/modules/ladspa.h new file mode 100644 index 00000000..b1a9c4e5 --- /dev/null +++ b/src/modules/ladspa.h @@ -0,0 +1,603 @@ +/* ladspa.h + + Linux Audio Developer's Simple Plugin API Version 1.1[LGPL]. + Copyright (C) 2000-2002 Richard W.E. Furse, Paul Barton-Davis, + Stefan Westerfeld. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public License + as published by the Free Software Foundation; either version 2.1 of + the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. */ + +#ifndef LADSPA_INCLUDED +#define LADSPA_INCLUDED + +#define LADSPA_VERSION "1.1" +#define LADSPA_VERSION_MAJOR 1 +#define LADSPA_VERSION_MINOR 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/*****************************************************************************/ + +/* Overview: + + There is a large number of synthesis packages in use or development + on the Linux platform at this time. This API (`The Linux Audio + Developer's Simple Plugin API') attempts to give programmers the + ability to write simple `plugin' audio processors in C/C++ and link + them dynamically (`plug') into a range of these packages (`hosts'). + It should be possible for any host and any plugin to communicate + completely through this interface. + + This API is deliberately short and simple. To achieve compatibility + with a range of promising Linux sound synthesis packages it + attempts to find the `greatest common divisor' in their logical + behaviour. Having said this, certain limiting decisions are + implicit, notably the use of a fixed type (LADSPA_Data) for all + data transfer and absence of a parameterised `initialisation' + phase. See below for the LADSPA_Data typedef. + + Plugins are expected to distinguish between control and audio + data. Plugins have `ports' that are inputs or outputs for audio or + control data and each plugin is `run' for a `block' corresponding + to a short time interval measured in samples. Audio data is + communicated using arrays of LADSPA_Data, allowing a block of audio + to be processed by the plugin in a single pass. Control data is + communicated using single LADSPA_Data values. Control data has a + single value at the start of a call to the `run()' or `run_adding()' + function, and may be considered to remain this value for its + duration. The plugin may assume that all its input and output ports + have been connected to the relevant data location (see the + `connect_port()' function below) before it is asked to run. + + Plugins will reside in shared object files suitable for dynamic + linking by dlopen() and family. The file will provide a number of + `plugin types' that can be used to instantiate actual plugins + (sometimes known as `plugin instances') that can be connected + together to perform tasks. + + This API contains very limited error-handling. */ + +/*****************************************************************************/ + +/* Fundamental data type passed in and out of plugin. This data type + is used to communicate audio samples and control values. It is + assumed that the plugin will work sensibly given any numeric input + value although it may have a preferred range (see hints below). + + For audio it is generally assumed that 1.0f is the `0dB' reference + amplitude and is a `normal' signal level. */ + +typedef float LADSPA_Data; + +/*****************************************************************************/ + +/* Special Plugin Properties: + + Optional features of the plugin type are encapsulated in the + LADSPA_Properties type. This is assembled by ORing individual + properties together. */ + +typedef int LADSPA_Properties; + +/* Property LADSPA_PROPERTY_REALTIME indicates that the plugin has a + real-time dependency (e.g. listens to a MIDI device) and so its + output must not be cached or subject to significant latency. */ +#define LADSPA_PROPERTY_REALTIME 0x1 + +/* Property LADSPA_PROPERTY_INPLACE_BROKEN indicates that the plugin + may cease to work correctly if the host elects to use the same data + location for both input and output (see connect_port()). This + should be avoided as enabling this flag makes it impossible for + hosts to use the plugin to process audio `in-place.' */ +#define LADSPA_PROPERTY_INPLACE_BROKEN 0x2 + +/* Property LADSPA_PROPERTY_HARD_RT_CAPABLE indicates that the plugin + is capable of running not only in a conventional host but also in a + `hard real-time' environment. To qualify for this the plugin must + satisfy all of the following: + + (1) The plugin must not use malloc(), free() or other heap memory + management within its run() or run_adding() functions. All new + memory used in run() must be managed via the stack. These + restrictions only apply to the run() function. + + (2) The plugin will not attempt to make use of any library + functions with the exceptions of functions in the ANSI standard C + and C maths libraries, which the host is expected to provide. + + (3) The plugin will not access files, devices, pipes, sockets, IPC + or any other mechanism that might result in process or thread + blocking. + + (4) The plugin will take an amount of time to execute a run() or + run_adding() call approximately of form (A+B*SampleCount) where A + and B depend on the machine and host in use. This amount of time + may not depend on input signals or plugin state. The host is left + the responsibility to perform timings to estimate upper bounds for + A and B. */ +#define LADSPA_PROPERTY_HARD_RT_CAPABLE 0x4 + +#define LADSPA_IS_REALTIME(x) ((x) & LADSPA_PROPERTY_REALTIME) +#define LADSPA_IS_INPLACE_BROKEN(x) ((x) & LADSPA_PROPERTY_INPLACE_BROKEN) +#define LADSPA_IS_HARD_RT_CAPABLE(x) ((x) & LADSPA_PROPERTY_HARD_RT_CAPABLE) + +/*****************************************************************************/ + +/* Plugin Ports: + + Plugins have `ports' that are inputs or outputs for audio or + data. Ports can communicate arrays of LADSPA_Data (for audio + inputs/outputs) or single LADSPA_Data values (for control + input/outputs). This information is encapsulated in the + LADSPA_PortDescriptor type which is assembled by ORing individual + properties together. + + Note that a port must be an input or an output port but not both + and that a port must be a control or audio port but not both. */ + +typedef int LADSPA_PortDescriptor; + +/* Property LADSPA_PORT_INPUT indicates that the port is an input. */ +#define LADSPA_PORT_INPUT 0x1 + +/* Property LADSPA_PORT_OUTPUT indicates that the port is an output. */ +#define LADSPA_PORT_OUTPUT 0x2 + +/* Property LADSPA_PORT_CONTROL indicates that the port is a control + port. */ +#define LADSPA_PORT_CONTROL 0x4 + +/* Property LADSPA_PORT_AUDIO indicates that the port is a audio + port. */ +#define LADSPA_PORT_AUDIO 0x8 + +#define LADSPA_IS_PORT_INPUT(x) ((x) & LADSPA_PORT_INPUT) +#define LADSPA_IS_PORT_OUTPUT(x) ((x) & LADSPA_PORT_OUTPUT) +#define LADSPA_IS_PORT_CONTROL(x) ((x) & LADSPA_PORT_CONTROL) +#define LADSPA_IS_PORT_AUDIO(x) ((x) & LADSPA_PORT_AUDIO) + +/*****************************************************************************/ + +/* Plugin Port Range Hints: + + The host may wish to provide a representation of data entering or + leaving a plugin (e.g. to generate a GUI automatically). To make + this more meaningful, the plugin should provide `hints' to the host + describing the usual values taken by the data. + + Note that these are only hints. The host may ignore them and the + plugin must not assume that data supplied to it is meaningful. If + the plugin receives invalid input data it is expected to continue + to run without failure and, where possible, produce a sensible + output (e.g. a high-pass filter given a negative cutoff frequency + might switch to an all-pass mode). + + Hints are meaningful for all input and output ports but hints for + input control ports are expected to be particularly useful. + + More hint information is encapsulated in the + LADSPA_PortRangeHintDescriptor type which is assembled by ORing + individual hint types together. Hints may require further + LowerBound and UpperBound information. + + All the hint information for a particular port is aggregated in the + LADSPA_PortRangeHint structure. */ + +typedef int LADSPA_PortRangeHintDescriptor; + +/* Hint LADSPA_HINT_BOUNDED_BELOW indicates that the LowerBound field + of the LADSPA_PortRangeHint should be considered meaningful. The + value in this field should be considered the (inclusive) lower + bound of the valid range. If LADSPA_HINT_SAMPLE_RATE is also + specified then the value of LowerBound should be multiplied by the + sample rate. */ +#define LADSPA_HINT_BOUNDED_BELOW 0x1 + +/* Hint LADSPA_HINT_BOUNDED_ABOVE indicates that the UpperBound field + of the LADSPA_PortRangeHint should be considered meaningful. The + value in this field should be considered the (inclusive) upper + bound of the valid range. If LADSPA_HINT_SAMPLE_RATE is also + specified then the value of UpperBound should be multiplied by the + sample rate. */ +#define LADSPA_HINT_BOUNDED_ABOVE 0x2 + +/* Hint LADSPA_HINT_TOGGLED indicates that the data item should be + considered a Boolean toggle. Data less than or equal to zero should + be considered `off' or `false,' and data above zero should be + considered `on' or `true.' LADSPA_HINT_TOGGLED may not be used in + conjunction with any other hint except LADSPA_HINT_DEFAULT_0 or + LADSPA_HINT_DEFAULT_1. */ +#define LADSPA_HINT_TOGGLED 0x4 + +/* Hint LADSPA_HINT_SAMPLE_RATE indicates that any bounds specified + should be interpreted as multiples of the sample rate. For + instance, a frequency range from 0Hz to the Nyquist frequency (half + the sample rate) could be requested by this hint in conjunction + with LowerBound = 0 and UpperBound = 0.5. Hosts that support bounds + at all must support this hint to retain meaning. */ +#define LADSPA_HINT_SAMPLE_RATE 0x8 + +/* Hint LADSPA_HINT_LOGARITHMIC indicates that it is likely that the + user will find it more intuitive to view values using a logarithmic + scale. This is particularly useful for frequencies and gains. */ +#define LADSPA_HINT_LOGARITHMIC 0x10 + +/* Hint LADSPA_HINT_INTEGER indicates that a user interface would + probably wish to provide a stepped control taking only integer + values. Any bounds set should be slightly wider than the actual + integer range required to avoid floating point rounding errors. For + instance, the integer set {0,1,2,3} might be described as [-0.1, + 3.1]. */ +#define LADSPA_HINT_INTEGER 0x20 + +/* The various LADSPA_HINT_HAS_DEFAULT_* hints indicate a `normal' + value for the port that is sensible as a default. For instance, + this value is suitable for use as an initial value in a user + interface or as a value the host might assign to a control port + when the user has not provided one. Defaults are encoded using a + mask so only one default may be specified for a port. Some of the + hints make use of lower and upper bounds, in which case the + relevant bound or bounds must be available and + LADSPA_HINT_SAMPLE_RATE must be applied as usual. The resulting + default must be rounded if LADSPA_HINT_INTEGER is present. Default + values were introduced in LADSPA v1.1. */ +#define LADSPA_HINT_DEFAULT_MASK 0x3C0 + +/* This default values indicates that no default is provided. */ +#define LADSPA_HINT_DEFAULT_NONE 0x0 + +/* This default hint indicates that the suggested lower bound for the + port should be used. */ +#define LADSPA_HINT_DEFAULT_MINIMUM 0x40 + +/* This default hint indicates that a low value between the suggested + lower and upper bounds should be chosen. For ports with + LADSPA_HINT_LOGARITHMIC, this should be exp(log(lower) * 0.75 + + log(upper) * 0.25). Otherwise, this should be (lower * 0.75 + upper + * 0.25). */ +#define LADSPA_HINT_DEFAULT_LOW 0x80 + +/* This default hint indicates that a middle value between the + suggested lower and upper bounds should be chosen. For ports with + LADSPA_HINT_LOGARITHMIC, this should be exp(log(lower) * 0.5 + + log(upper) * 0.5). Otherwise, this should be (lower * 0.5 + upper * + 0.5). */ +#define LADSPA_HINT_DEFAULT_MIDDLE 0xC0 + +/* This default hint indicates that a high value between the suggested + lower and upper bounds should be chosen. For ports with + LADSPA_HINT_LOGARITHMIC, this should be exp(log(lower) * 0.25 + + log(upper) * 0.75). Otherwise, this should be (lower * 0.25 + upper + * 0.75). */ +#define LADSPA_HINT_DEFAULT_HIGH 0x100 + +/* This default hint indicates that the suggested upper bound for the + port should be used. */ +#define LADSPA_HINT_DEFAULT_MAXIMUM 0x140 + +/* This default hint indicates that the number 0 should be used. Note + that this default may be used in conjunction with + LADSPA_HINT_TOGGLED. */ +#define LADSPA_HINT_DEFAULT_0 0x200 + +/* This default hint indicates that the number 1 should be used. Note + that this default may be used in conjunction with + LADSPA_HINT_TOGGLED. */ +#define LADSPA_HINT_DEFAULT_1 0x240 + +/* This default hint indicates that the number 100 should be used. */ +#define LADSPA_HINT_DEFAULT_100 0x280 + +/* This default hint indicates that the Hz frequency of `concert A' + should be used. This will be 440 unless the host uses an unusual + tuning convention, in which case it may be within a few Hz. */ +#define LADSPA_HINT_DEFAULT_440 0x2C0 + +#define LADSPA_IS_HINT_BOUNDED_BELOW(x) ((x) & LADSPA_HINT_BOUNDED_BELOW) +#define LADSPA_IS_HINT_BOUNDED_ABOVE(x) ((x) & LADSPA_HINT_BOUNDED_ABOVE) +#define LADSPA_IS_HINT_TOGGLED(x) ((x) & LADSPA_HINT_TOGGLED) +#define LADSPA_IS_HINT_SAMPLE_RATE(x) ((x) & LADSPA_HINT_SAMPLE_RATE) +#define LADSPA_IS_HINT_LOGARITHMIC(x) ((x) & LADSPA_HINT_LOGARITHMIC) +#define LADSPA_IS_HINT_INTEGER(x) ((x) & LADSPA_HINT_INTEGER) + +#define LADSPA_IS_HINT_HAS_DEFAULT(x) ((x) & LADSPA_HINT_DEFAULT_MASK) +#define LADSPA_IS_HINT_DEFAULT_MINIMUM(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_MINIMUM) +#define LADSPA_IS_HINT_DEFAULT_LOW(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_LOW) +#define LADSPA_IS_HINT_DEFAULT_MIDDLE(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_MIDDLE) +#define LADSPA_IS_HINT_DEFAULT_HIGH(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_HIGH) +#define LADSPA_IS_HINT_DEFAULT_MAXIMUM(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_MAXIMUM) +#define LADSPA_IS_HINT_DEFAULT_0(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_0) +#define LADSPA_IS_HINT_DEFAULT_1(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_1) +#define LADSPA_IS_HINT_DEFAULT_100(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_100) +#define LADSPA_IS_HINT_DEFAULT_440(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_440) + +typedef struct _LADSPA_PortRangeHint { + + /* Hints about the port. */ + LADSPA_PortRangeHintDescriptor HintDescriptor; + + /* Meaningful when hint LADSPA_HINT_BOUNDED_BELOW is active. When + LADSPA_HINT_SAMPLE_RATE is also active then this value should be + multiplied by the relevant sample rate. */ + LADSPA_Data LowerBound; + + /* Meaningful when hint LADSPA_HINT_BOUNDED_ABOVE is active. When + LADSPA_HINT_SAMPLE_RATE is also active then this value should be + multiplied by the relevant sample rate. */ + LADSPA_Data UpperBound; + +} LADSPA_PortRangeHint; + +/*****************************************************************************/ + +/* Plugin Handles: + + This plugin handle indicates a particular instance of the plugin + concerned. It is valid to compare this to NULL (0 for C++) but + otherwise the host should not attempt to interpret it. The plugin + may use it to reference internal instance data. */ + +typedef void * LADSPA_Handle; + +/*****************************************************************************/ + +/* Descriptor for a Type of Plugin: + + This structure is used to describe a plugin type. It provides a + number of functions to examine the type, instantiate it, link it to + buffers and workspaces and to run it. */ + +typedef struct _LADSPA_Descriptor { + + /* This numeric identifier indicates the plugin type + uniquely. Plugin programmers may reserve ranges of IDs from a + central body to avoid clashes. Hosts may assume that IDs are + below 0x1000000. */ + unsigned long UniqueID; + + /* This identifier can be used as a unique, case-sensitive + identifier for the plugin type within the plugin file. Plugin + types should be identified by file and label rather than by index + or plugin name, which may be changed in new plugin + versions. Labels must not contain white-space characters. */ + const char * Label; + + /* This indicates a number of properties of the plugin. */ + LADSPA_Properties Properties; + + /* This member points to the null-terminated name of the plugin + (e.g. "Sine Oscillator"). */ + const char * Name; + + /* This member points to the null-terminated string indicating the + maker of the plugin. This can be an empty string but not NULL. */ + const char * Maker; + + /* This member points to the null-terminated string indicating any + copyright applying to the plugin. If no Copyright applies the + string "None" should be used. */ + const char * Copyright; + + /* This indicates the number of ports (input AND output) present on + the plugin. */ + unsigned long PortCount; + + /* This member indicates an array of port descriptors. Valid indices + vary from 0 to PortCount-1. */ + const LADSPA_PortDescriptor * PortDescriptors; + + /* This member indicates an array of null-terminated strings + describing ports (e.g. "Frequency (Hz)"). Valid indices vary from + 0 to PortCount-1. */ + const char * const * PortNames; + + /* This member indicates an array of range hints for each port (see + above). Valid indices vary from 0 to PortCount-1. */ + const LADSPA_PortRangeHint * PortRangeHints; + + /* This may be used by the plugin developer to pass any custom + implementation data into an instantiate call. It must not be used + or interpreted by the host. It is expected that most plugin + writers will not use this facility as LADSPA_Handle should be + used to hold instance data. */ + void * ImplementationData; + + /* This member is a function pointer that instantiates a plugin. A + handle is returned indicating the new plugin instance. The + instantiation function accepts a sample rate as a parameter. The + plugin descriptor from which this instantiate function was found + must also be passed. This function must return NULL if + instantiation fails. + + Note that instance initialisation should generally occur in + activate() rather than here. */ + LADSPA_Handle (*instantiate)(const struct _LADSPA_Descriptor * Descriptor, + unsigned long SampleRate); + + /* This member is a function pointer that connects a port on an + instantiated plugin to a memory location at which a block of data + for the port will be read/written. The data location is expected + to be an array of LADSPA_Data for audio ports or a single + LADSPA_Data value for control ports. Memory issues will be + managed by the host. The plugin must read/write the data at these + locations every time run() or run_adding() is called and the data + present at the time of this connection call should not be + considered meaningful. + + connect_port() may be called more than once for a plugin instance + to allow the host to change the buffers that the plugin is + reading or writing. These calls may be made before or after + activate() or deactivate() calls. + + connect_port() must be called at least once for each port before + run() or run_adding() is called. When working with blocks of + LADSPA_Data the plugin should pay careful attention to the block + size passed to the run function as the block allocated may only + just be large enough to contain the block of samples. + + Plugin writers should be aware that the host may elect to use the + same buffer for more than one port and even use the same buffer + for both input and output (see LADSPA_PROPERTY_INPLACE_BROKEN). + However, overlapped buffers or use of a single buffer for both + audio and control data may result in unexpected behaviour. */ + void (*connect_port)(LADSPA_Handle Instance, + unsigned long Port, + LADSPA_Data * DataLocation); + + /* This member is a function pointer that initialises a plugin + instance and activates it for use. This is separated from + instantiate() to aid real-time support and so that hosts can + reinitialise a plugin instance by calling deactivate() and then + activate(). In this case the plugin instance must reset all state + information dependent on the history of the plugin instance + except for any data locations provided by connect_port() and any + gain set by set_run_adding_gain(). If there is nothing for + activate() to do then the plugin writer may provide a NULL rather + than an empty function. + + When present, hosts must call this function once before run() (or + run_adding()) is called for the first time. This call should be + made as close to the run() call as possible and indicates to + real-time plugins that they are now live. Plugins should not rely + on a prompt call to run() after activate(). activate() may not be + called again unless deactivate() is called first. Note that + connect_port() may be called before or after a call to + activate(). */ + void (*activate)(LADSPA_Handle Instance); + + /* This method is a function pointer that runs an instance of a + plugin for a block. Two parameters are required: the first is a + handle to the particular instance to be run and the second + indicates the block size (in samples) for which the plugin + instance may run. + + Note that if an activate() function exists then it must be called + before run() or run_adding(). If deactivate() is called for a + plugin instance then the plugin instance may not be reused until + activate() has been called again. + + If the plugin has the property LADSPA_PROPERTY_HARD_RT_CAPABLE + then there are various things that the plugin should not do + within the run() or run_adding() functions (see above). */ + void (*run)(LADSPA_Handle Instance, + unsigned long SampleCount); + + /* This method is a function pointer that runs an instance of a + plugin for a block. This has identical behaviour to run() except + in the way data is output from the plugin. When run() is used, + values are written directly to the memory areas associated with + the output ports. However when run_adding() is called, values + must be added to the values already present in the memory + areas. Furthermore, output values written must be scaled by the + current gain set by set_run_adding_gain() (see below) before + addition. + + run_adding() is optional. When it is not provided by a plugin, + this function pointer must be set to NULL. When it is provided, + the function set_run_adding_gain() must be provided also. */ + void (*run_adding)(LADSPA_Handle Instance, + unsigned long SampleCount); + + /* This method is a function pointer that sets the output gain for + use when run_adding() is called (see above). If this function is + never called the gain is assumed to default to 1. Gain + information should be retained when activate() or deactivate() + are called. + + This function should be provided by the plugin if and only if the + run_adding() function is provided. When it is absent this + function pointer must be set to NULL. */ + void (*set_run_adding_gain)(LADSPA_Handle Instance, + LADSPA_Data Gain); + + /* This is the counterpart to activate() (see above). If there is + nothing for deactivate() to do then the plugin writer may provide + a NULL rather than an empty function. + + Hosts must deactivate all activated units after they have been + run() (or run_adding()) for the last time. This call should be + made as close to the last run() call as possible and indicates to + real-time plugins that they are no longer live. Plugins should + not rely on prompt deactivation. Note that connect_port() may be + called before or after a call to deactivate(). + + Deactivation is not similar to pausing as the plugin instance + will be reinitialised when activate() is called to reuse it. */ + void (*deactivate)(LADSPA_Handle Instance); + + /* Once an instance of a plugin has been finished with it can be + deleted using the following function. The instance handle passed + ceases to be valid after this call. + + If activate() was called for a plugin instance then a + corresponding call to deactivate() must be made before cleanup() + is called. */ + void (*cleanup)(LADSPA_Handle Instance); + +} LADSPA_Descriptor; + +/**********************************************************************/ + +/* Accessing a Plugin: */ + +/* The exact mechanism by which plugins are loaded is host-dependent, + however all most hosts will need to know is the name of shared + object file containing the plugin types. To allow multiple hosts to + share plugin types, hosts may wish to check for environment + variable LADSPA_PATH. If present, this should contain a + colon-separated path indicating directories that should be searched + (in order) when loading plugin types. + + A plugin programmer must include a function called + "ladspa_descriptor" with the following function prototype within + the shared object file. This function will have C-style linkage (if + you are using C++ this is taken care of by the `extern "C"' clause + at the top of the file). + + A host will find the plugin shared object file by one means or + another, find the ladspa_descriptor() function, call it, and + proceed from there. + + Plugin types are accessed by index (not ID) using values from 0 + upwards. Out of range indexes must result in this function + returning NULL, so the plugin count can be determined by checking + for the least index that results in NULL being returned. */ + +const LADSPA_Descriptor * ladspa_descriptor(unsigned long Index); + +/* Datatype corresponding to the ladspa_descriptor() function. */ +typedef const LADSPA_Descriptor * +(*LADSPA_Descriptor_Function)(unsigned long Index); + +/**********************************************************************/ + +#ifdef __cplusplus +} +#endif + +#endif /* LADSPA_INCLUDED */ + +/* EOF */ diff --git a/src/modules/module-alsa-sink.c b/src/modules/module-alsa-sink.c index 3d9f7577..a09247fe 100644 --- a/src/modules/module-alsa-sink.c +++ b/src/modules/module-alsa-sink.c @@ -26,18 +26,12 @@ #include <config.h> #endif -#include <assert.h> #include <stdio.h> -#ifdef HAVE_SYS_POLL_H -#include <sys/poll.h> -#else -#include "poll.h" -#endif - #include <asoundlib.h> #include <pulse/xmalloc.h> +#include <pulse/util.h> #include <pulsecore/core.h> #include <pulsecore/module.h> @@ -47,6 +41,11 @@ #include <pulsecore/core-util.h> #include <pulsecore/sample-util.h> #include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/thread.h> +#include <pulsecore/core-error.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/rtpoll.h> #include "alsa-util.h" #include "module-alsa-sink-symdef.h" @@ -62,20 +61,38 @@ PA_MODULE_USAGE( "rate=<sample rate> " "fragments=<number of fragments> " "fragment_size=<fragment size> " - "channel_map=<channel map>") + "channel_map=<channel map> " + "mmap=<enable memory mapping?>") + +#define DEFAULT_DEVICE "default" struct userdata { + pa_core *core; + pa_module *module; + pa_sink *sink; + + pa_thread *thread; + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; + snd_pcm_t *pcm_handle; + + pa_alsa_fdlist *mixer_fdl; snd_mixer_t *mixer_handle; snd_mixer_elem_t *mixer_elem; - pa_sink *sink; - struct pa_alsa_fdlist *pcm_fdl; - struct pa_alsa_fdlist *mixer_fdl; long hw_volume_max, hw_volume_min; - size_t frame_size, fragment_size; - pa_memchunk memchunk, silence; - pa_module *module; + size_t frame_size, fragment_size, hwbuf_size; + unsigned nfragments; + pa_memchunk memchunk; + + char *device_name; + + int use_mmap; + + int first; + + pa_rtpoll_item *alsa_rtpoll_item; }; static const char* const valid_modargs[] = { @@ -87,260 +104,440 @@ static const char* const valid_modargs[] = { "fragments", "fragment_size", "channel_map", + "mmap", NULL }; -#define DEFAULT_DEVICE "default" +static int mmap_write(struct userdata *u) { + int work_done = 0; -static void update_usage(struct userdata *u) { - pa_module_set_used(u->module, u->sink ? pa_sink_used_by(u->sink) : 0); -} + pa_assert(u); + pa_sink_assert_ref(u->sink); -static void clear_up(struct userdata *u) { - assert(u); + for (;;) { + pa_memchunk chunk; + void *p; + snd_pcm_sframes_t n; + int err; + const snd_pcm_channel_area_t *areas; + snd_pcm_uframes_t offset, frames; + + if ((n = snd_pcm_avail_update(u->pcm_handle)) < 0) { + + if (n == -EPIPE) { + pa_log_debug("snd_pcm_avail_update: Buffer underrun!"); + u->first = 1; + } - if (u->sink) { - pa_sink_disconnect(u->sink); - pa_sink_unref(u->sink); - u->sink = NULL; - } + if ((err = snd_pcm_recover(u->pcm_handle, n, 1)) == 0) + continue; - if (u->pcm_fdl) - pa_alsa_fdlist_free(u->pcm_fdl); - if (u->mixer_fdl) - pa_alsa_fdlist_free(u->mixer_fdl); + if (err == -EAGAIN) + return work_done; - u->pcm_fdl = u->mixer_fdl = NULL; + pa_log("snd_pcm_avail_update: %s", snd_strerror(err)); + return -1; + } - if (u->mixer_handle) { - snd_mixer_close(u->mixer_handle); - u->mixer_handle = NULL; - } +/* pa_log("Got request for %i samples", (int) n); */ - if (u->pcm_handle) { - snd_pcm_drop(u->pcm_handle); - snd_pcm_close(u->pcm_handle); - u->pcm_handle = NULL; - } -} + if (n <= 0) + return work_done; -static int xrun_recovery(struct userdata *u) { - int ret; - assert(u); + frames = n; - pa_log_info("*** ALSA-XRUN (playback) ***"); + if ((err = snd_pcm_mmap_begin(u->pcm_handle, &areas, &offset, &frames)) < 0) { - if ((ret = snd_pcm_prepare(u->pcm_handle)) < 0) { - pa_log("snd_pcm_prepare() failed: %s", snd_strerror(-ret)); + if (err == -EPIPE) { + pa_log_debug("snd_pcm_mmap_begin: Buffer underrun!"); + u->first = 1; + } - clear_up(u); - pa_module_unload_request(u->module); - return -1; - } + if ((err = snd_pcm_recover(u->pcm_handle, err, 1)) == 0) + continue; - return ret; -} + if (err == -EAGAIN) + return work_done; -static int suspend_recovery(struct userdata *u) { - int ret; - assert(u); + pa_log("Failed to write data to DSP: %s", snd_strerror(err)); + return -1; + } - pa_log_info("*** ALSA-SUSPEND (playback) ***"); + /* Check these are multiples of 8 bit */ + pa_assert((areas[0].first & 7) == 0); + pa_assert((areas[0].step & 7)== 0); - if ((ret = snd_pcm_resume(u->pcm_handle)) < 0) { - if (ret == -EAGAIN) - return -1; + /* We assume a single interleaved memory buffer */ + pa_assert((areas[0].first >> 3) == 0); + pa_assert((areas[0].step >> 3) == u->frame_size); - if (ret != -ENOSYS) - pa_log("snd_pcm_resume() failed: %s", snd_strerror(-ret)); - else { - if ((ret = snd_pcm_prepare(u->pcm_handle)) < 0) - pa_log("snd_pcm_prepare() failed: %s", snd_strerror(-ret)); - } + p = (uint8_t*) areas[0].addr + (offset * u->frame_size); + + chunk.memblock = pa_memblock_new_fixed(u->core->mempool, p, frames * u->frame_size, 1); + chunk.length = pa_memblock_get_length(chunk.memblock); + chunk.index = 0; + + pa_sink_render_into_full(u->sink, &chunk); + + /* FIXME: Maybe we can do something to keep this memory block + * a little bit longer around? */ + pa_memblock_unref_fixed(chunk.memblock); + + if ((err = snd_pcm_mmap_commit(u->pcm_handle, offset, frames)) < 0) { + + if (err == -EPIPE) { + pa_log_debug("snd_pcm_mmap_commit: Buffer underrun!"); + u->first = 1; + } + + if ((err = snd_pcm_recover(u->pcm_handle, err, 1)) == 0) + continue; - if (ret < 0) { - clear_up(u); - pa_module_unload_request(u->module); + if (err == -EAGAIN) + return work_done; + + pa_log("Failed to write data to DSP: %s", snd_strerror(err)); return -1; } - } - return ret; + work_done = 1; + + if (frames >= (snd_pcm_uframes_t) n) + return work_done; + +/* pa_log("wrote %i samples", (int) frames); */ + } } -static void do_write(struct userdata *u) { - assert(u); +static int unix_write(struct userdata *u) { + snd_pcm_status_t *status; + int work_done = 0; - update_usage(u); + snd_pcm_status_alloca(&status); + + pa_assert(u); + pa_sink_assert_ref(u->sink); for (;;) { - pa_memchunk *memchunk = NULL; - snd_pcm_sframes_t frames; - - if (u->memchunk.memblock) - memchunk = &u->memchunk; - else { - if (pa_sink_render(u->sink, u->fragment_size, &u->memchunk) < 0) - memchunk = &u->silence; - else - memchunk = &u->memchunk; + void *p; + snd_pcm_sframes_t t; + ssize_t l; + int err; + + if ((err = snd_pcm_status(u->pcm_handle, status)) < 0) { + pa_log("Failed to query DSP status data: %s", snd_strerror(err)); + return -1; } - assert(memchunk->memblock); - assert(memchunk->memblock->data); - assert(memchunk->length); - assert(memchunk->memblock->length); - assert((memchunk->length % u->frame_size) == 0); + if (snd_pcm_status_get_avail_max(status)*u->frame_size >= u->hwbuf_size) + pa_log_debug("Buffer underrun!"); - if ((frames = snd_pcm_writei(u->pcm_handle, (uint8_t*) memchunk->memblock->data + memchunk->index, memchunk->length / u->frame_size)) < 0) { - if (frames == -EAGAIN) - return; + l = snd_pcm_status_get_avail(status) * u->frame_size; - if (frames == -EPIPE) { - if (xrun_recovery(u) < 0) - return; +/* pa_log("%u bytes to write", l); */ - continue; - } + if (l <= 0) + return work_done; + + if (u->memchunk.length <= 0) + pa_sink_render(u->sink, l, &u->memchunk); + + pa_assert(u->memchunk.length > 0); - if (frames == -ESTRPIPE) { - if (suspend_recovery(u) < 0) - return; + p = pa_memblock_acquire(u->memchunk.memblock); + t = snd_pcm_writei(u->pcm_handle, (const uint8_t*) p + u->memchunk.index, u->memchunk.length / u->frame_size); + pa_memblock_release(u->memchunk.memblock); +/* pa_log("wrote %i bytes of %u (%u)", t*u->frame_size, u->memchunk.length, l); */ + + pa_assert(t != 0); + + if (t < 0) { + + if ((t = snd_pcm_recover(u->pcm_handle, t, 1)) == 0) continue; + + if (t == -EAGAIN) { + pa_log_debug("EAGAIN"); + return work_done; + } else { + pa_log("Failed to write data to DSP: %s", snd_strerror(t)); + return -1; } + } - pa_log("snd_pcm_writei() failed: %s", snd_strerror(-frames)); + u->memchunk.index += t * u->frame_size; + u->memchunk.length -= t * u->frame_size; - clear_up(u); - pa_module_unload_request(u->module); - return; + if (u->memchunk.length <= 0) { + pa_memblock_unref(u->memchunk.memblock); + pa_memchunk_reset(&u->memchunk); } - if (memchunk == &u->memchunk) { - size_t l = frames * u->frame_size; - memchunk->index += l; - memchunk->length -= l; + work_done = 1; - if (memchunk->length == 0) { - pa_memblock_unref(memchunk->memblock); - memchunk->memblock = NULL; - memchunk->index = memchunk->length = 0; - } - } + if (t * u->frame_size >= (unsigned) l) + return work_done; + } +} - break; +static pa_usec_t sink_get_latency(struct userdata *u) { + pa_usec_t r = 0; + snd_pcm_status_t *status; + snd_pcm_sframes_t frames = 0; + int err; + + snd_pcm_status_alloca(&status); + + pa_assert(u); + pa_assert(u->pcm_handle); + + if ((err = snd_pcm_status(u->pcm_handle, status)) < 0) + pa_log("Failed to get delay: %s", snd_strerror(err)); + else + frames = snd_pcm_status_get_delay(status); + + if (frames > 0) + r = pa_bytes_to_usec(frames * u->frame_size, &u->sink->sample_spec); + + if (u->memchunk.memblock) + r += pa_bytes_to_usec(u->memchunk.length, &u->sink->sample_spec); + + return r; +} + +static int build_pollfd(struct userdata *u) { + int err; + struct pollfd *pollfd; + int n; + + pa_assert(u); + pa_assert(u->pcm_handle); + + if ((n = snd_pcm_poll_descriptors_count(u->pcm_handle)) < 0) { + pa_log("snd_pcm_poll_descriptors_count() failed: %s", snd_strerror(n)); + return -1; + } + + if (u->alsa_rtpoll_item) + pa_rtpoll_item_free(u->alsa_rtpoll_item); + + u->alsa_rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, n); + pollfd = pa_rtpoll_item_get_pollfd(u->alsa_rtpoll_item, NULL); + + if ((err = snd_pcm_poll_descriptors(u->pcm_handle, pollfd, n)) < 0) { + pa_log("snd_pcm_poll_descriptors() failed: %s", snd_strerror(err)); + return -1; } + + return 0; } -static void fdl_callback(void *userdata) { - struct userdata *u = userdata; - assert(u); +static int suspend(struct userdata *u) { + pa_assert(u); + pa_assert(u->pcm_handle); - if (snd_pcm_state(u->pcm_handle) == SND_PCM_STATE_XRUN) - if (xrun_recovery(u) < 0) - return; + /* Let's suspend */ + snd_pcm_drain(u->pcm_handle); + snd_pcm_close(u->pcm_handle); + u->pcm_handle = NULL; - if (snd_pcm_state(u->pcm_handle) == SND_PCM_STATE_SUSPENDED) - if (suspend_recovery(u) < 0) - return; + if (u->alsa_rtpoll_item) { + pa_rtpoll_item_free(u->alsa_rtpoll_item); + u->alsa_rtpoll_item = NULL; + } + + pa_log_info("Device suspended..."); - do_write(u); + return 0; } -static int mixer_callback(snd_mixer_elem_t *elem, unsigned int mask) { - struct userdata *u = snd_mixer_elem_get_callback_private(elem); +static int unsuspend(struct userdata *u) { + pa_sample_spec ss; + int err, b; + unsigned nfrags; + snd_pcm_uframes_t period_size; - assert(u && u->mixer_handle); + pa_assert(u); + pa_assert(!u->pcm_handle); - if (mask == SND_CTL_EVENT_MASK_REMOVE) - return 0; + pa_log_info("Trying resume..."); - if (mask & SND_CTL_EVENT_MASK_VALUE) { - if (u->sink->get_hw_volume) - u->sink->get_hw_volume(u->sink); - if (u->sink->get_hw_mute) - u->sink->get_hw_mute(u->sink); - pa_subscription_post(u->sink->core, - PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, - u->sink->index); + snd_config_update_free_global(); + if ((err = snd_pcm_open(&u->pcm_handle, u->device_name, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { + pa_log("Error opening PCM device %s: %s", u->device_name, snd_strerror(err)); + goto fail; + } + + ss = u->sink->sample_spec; + nfrags = u->nfragments; + period_size = u->fragment_size / u->frame_size; + b = u->use_mmap; + + if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &nfrags, &period_size, &b)) < 0) { + pa_log("Failed to set hardware parameters: %s", snd_strerror(err)); + goto fail; + } + + if (b != u->use_mmap) { + pa_log_warn("Resume failed, couldn't get original access mode."); + goto fail; + } + + if (!pa_sample_spec_equal(&ss, &u->sink->sample_spec)) { + pa_log_warn("Resume failed, couldn't restore original sample settings."); + goto fail; + } + + if (nfrags != u->nfragments || period_size*u->frame_size != u->fragment_size) { + pa_log_warn("Resume failed, couldn't restore original fragment settings."); + goto fail; + } + + if ((err = pa_alsa_set_sw_params(u->pcm_handle)) < 0) { + pa_log("Failed to set software parameters: %s", snd_strerror(err)); + goto fail; } + if (build_pollfd(u) < 0) + goto fail; + + /* FIXME: We need to reload the volume somehow */ + + u->first = 1; + + pa_log_info("Resumed successfully..."); + return 0; + +fail: + if (u->pcm_handle) { + snd_pcm_close(u->pcm_handle); + u->pcm_handle = NULL; + } + + return -1; } -static pa_usec_t sink_get_latency_cb(pa_sink *s) { - pa_usec_t r = 0; - struct userdata *u = s->userdata; - snd_pcm_sframes_t frames; - int err; +static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SINK(o)->userdata; - assert(s && u && u->sink); + switch (code) { - if ((err = snd_pcm_delay(u->pcm_handle, &frames)) < 0) { - pa_log("failed to get delay: %s", snd_strerror(err)); - s->get_latency = NULL; - return 0; + case PA_SINK_MESSAGE_GET_LATENCY: { + pa_usec_t r = 0; + + if (u->pcm_handle) + r = sink_get_latency(u); + + *((pa_usec_t*) data) = r; + + return 0; + } + + case PA_SINK_MESSAGE_SET_STATE: + + switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) { + + case PA_SINK_SUSPENDED: + pa_assert(PA_SINK_OPENED(u->sink->thread_info.state)); + + if (suspend(u) < 0) + return -1; + + break; + + case PA_SINK_IDLE: + case PA_SINK_RUNNING: + + if (u->sink->thread_info.state == PA_SINK_INIT) { + if (build_pollfd(u) < 0) + return -1; + } + + if (u->sink->thread_info.state == PA_SINK_SUSPENDED) { + if (unsuspend(u) < 0) + return -1; + } + + break; + + case PA_SINK_UNLINKED: + case PA_SINK_INIT: + ; + } + + break; } - if (frames < 0) - frames = 0; + return pa_sink_process_msg(o, code, data, offset, chunk); +} - r += pa_bytes_to_usec(frames * u->frame_size, &s->sample_spec); +static int mixer_callback(snd_mixer_elem_t *elem, unsigned int mask) { + struct userdata *u = snd_mixer_elem_get_callback_private(elem); - if (u->memchunk.memblock) - r += pa_bytes_to_usec(u->memchunk.length, &s->sample_spec); + pa_assert(u); + pa_assert(u->mixer_handle); - return r; + if (mask == SND_CTL_EVENT_MASK_REMOVE) + return 0; + + if (mask & SND_CTL_EVENT_MASK_VALUE) { + pa_sink_get_volume(u->sink); + pa_sink_get_mute(u->sink); + } + + return 0; } -static int sink_get_hw_volume_cb(pa_sink *s) { +static int sink_get_volume_cb(pa_sink *s) { struct userdata *u = s->userdata; int err; int i; - assert(u); - assert(u->mixer_elem); + pa_assert(u); + pa_assert(u->mixer_elem); - for (i = 0; i < s->hw_volume.channels; i++) { + for (i = 0; i < s->sample_spec.channels; i++) { long set_vol, vol; - assert(snd_mixer_selem_has_playback_channel(u->mixer_elem, i)); + pa_assert(snd_mixer_selem_has_playback_channel(u->mixer_elem, i)); if ((err = snd_mixer_selem_get_playback_volume(u->mixer_elem, i, &vol)) < 0) goto fail; - set_vol = (long) roundf(((float) s->hw_volume.values[i] * (u->hw_volume_max - u->hw_volume_min)) / PA_VOLUME_NORM) + u->hw_volume_min; + set_vol = (long) roundf(((float) s->volume.values[i] * (u->hw_volume_max - u->hw_volume_min)) / PA_VOLUME_NORM) + u->hw_volume_min; /* Try to avoid superfluous volume changes */ if (set_vol != vol) - s->hw_volume.values[i] = (pa_volume_t) roundf(((float) (vol - u->hw_volume_min) * PA_VOLUME_NORM) / (u->hw_volume_max - u->hw_volume_min)); + s->volume.values[i] = (pa_volume_t) roundf(((float) (vol - u->hw_volume_min) * PA_VOLUME_NORM) / (u->hw_volume_max - u->hw_volume_min)); } return 0; fail: pa_log_error("Unable to read volume: %s", snd_strerror(err)); - s->get_hw_volume = NULL; - s->set_hw_volume = NULL; + + s->get_volume = NULL; + s->set_volume = NULL; return -1; } -static int sink_set_hw_volume_cb(pa_sink *s) { +static int sink_set_volume_cb(pa_sink *s) { struct userdata *u = s->userdata; int err; int i; - pa_volume_t vol; - assert(u); - assert(u->mixer_elem); + pa_assert(u); + pa_assert(u->mixer_elem); - for (i = 0; i < s->hw_volume.channels; i++) { + for (i = 0; i < s->sample_spec.channels; i++) { long alsa_vol; + pa_volume_t vol; - assert(snd_mixer_selem_has_playback_channel(u->mixer_elem, i)); + pa_assert(snd_mixer_selem_has_playback_channel(u->mixer_elem, i)); - vol = s->hw_volume.values[i]; + vol = s->volume.values[i]; if (vol > PA_VOLUME_NORM) vol = PA_VOLUME_NORM; @@ -355,55 +552,166 @@ static int sink_set_hw_volume_cb(pa_sink *s) { fail: pa_log_error("Unable to set volume: %s", snd_strerror(err)); - s->get_hw_volume = NULL; - s->set_hw_volume = NULL; + + s->get_volume = NULL; + s->set_volume = NULL; return -1; } -static int sink_get_hw_mute_cb(pa_sink *s) { +static int sink_get_mute_cb(pa_sink *s) { struct userdata *u = s->userdata; int err, sw; - assert(u && u->mixer_elem); + pa_assert(u); + pa_assert(u->mixer_elem); - err = snd_mixer_selem_get_playback_switch(u->mixer_elem, 0, &sw); - if (err) { + if ((err = snd_mixer_selem_get_playback_switch(u->mixer_elem, 0, &sw)) < 0) { pa_log_error("Unable to get switch: %s", snd_strerror(err)); - s->get_hw_mute = NULL; - s->set_hw_mute = NULL; + + s->get_mute = NULL; + s->set_mute = NULL; return -1; } - s->hw_muted = !sw; + s->muted = !sw; return 0; } -static int sink_set_hw_mute_cb(pa_sink *s) { +static int sink_set_mute_cb(pa_sink *s) { struct userdata *u = s->userdata; int err; - assert(u && u->mixer_elem); + pa_assert(u); + pa_assert(u->mixer_elem); - err = snd_mixer_selem_set_playback_switch_all(u->mixer_elem, !s->hw_muted); - if (err) { + if ((err = snd_mixer_selem_set_playback_switch_all(u->mixer_elem, !s->muted)) < 0) { pa_log_error("Unable to set switch: %s", snd_strerror(err)); - s->get_hw_mute = NULL; - s->set_hw_mute = NULL; + + s->get_mute = NULL; + s->set_mute = NULL; return -1; } return 0; } -int pa__init(pa_core *c, pa_module*m) { +static void thread_func(void *userdata) { + struct userdata *u = userdata; + + pa_assert(u); + + pa_log_debug("Thread starting up"); + + if (u->core->high_priority) + pa_make_realtime(); + + pa_thread_mq_install(&u->thread_mq); + pa_rtpoll_install(u->rtpoll); + + for (;;) { + int ret; + + /* Render some data and write it to the dsp */ + if (PA_SINK_OPENED(u->sink->thread_info.state)) { + int work_done = 0; + + if (u->use_mmap) { + if ((work_done = mmap_write(u)) < 0) + goto fail; + } else { + if ((work_done = unix_write(u)) < 0) + goto fail; + } + + if (work_done && u->first) { + pa_log_info("Starting playback."); + snd_pcm_start(u->pcm_handle); + u->first = 0; + continue; + } + } + + /* Hmm, nothing to do. Let's sleep */ + if ((ret = pa_rtpoll_run(u->rtpoll, 1)) < 0) + goto fail; + + if (ret == 0) + goto finish; + + /* Tell ALSA about this and process its response */ + if (PA_SINK_OPENED(u->sink->thread_info.state)) { + struct pollfd *pollfd; + unsigned short revents = 0; + int err; + unsigned n; + + pollfd = pa_rtpoll_item_get_pollfd(u->alsa_rtpoll_item, &n); + + if ((err = snd_pcm_poll_descriptors_revents(u->pcm_handle, pollfd, n, &revents)) < 0) { + pa_log("snd_pcm_poll_descriptors_revents() failed: %s", snd_strerror(err)); + goto fail; + } + + if (revents & (POLLERR|POLLNVAL|POLLHUP)) { + + if (revents & POLLERR) + pa_log_warn("Got POLLERR from ALSA"); + if (revents & POLLNVAL) + pa_log_warn("Got POLLNVAL from ALSA"); + if (revents & POLLHUP) + pa_log_warn("Got POLLHUP from ALSA"); + + /* Try to recover from this error */ + + switch (snd_pcm_state(u->pcm_handle)) { + + case SND_PCM_STATE_XRUN: + if ((err = snd_pcm_recover(u->pcm_handle, -EPIPE, 1)) != 0) { + pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP and XRUN: %s", snd_strerror(err)); + goto fail; + } + break; + + case SND_PCM_STATE_SUSPENDED: + if ((err = snd_pcm_recover(u->pcm_handle, -ESTRPIPE, 1)) != 0) { + pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP and SUSPENDED: %s", snd_strerror(err)); + goto fail; + } + break; + + default: + + snd_pcm_drop(u->pcm_handle); + + if ((err = snd_pcm_prepare(u->pcm_handle)) < 0) { + pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP with snd_pcm_prepare(): %s", snd_strerror(err)); + goto fail; + } + break; + } + } + } + } + +fail: + /* If this was no regular exit from the loop we have to continue + * processing messages until we received PA_MESSAGE_SHUTDOWN */ + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + +finish: + pa_log_debug("Thread shutting down"); +} + +int pa__init(pa_module*m) { + pa_modargs *ma = NULL; - int ret = -1; struct userdata *u = NULL; - const char *dev; + char *dev; pa_sample_spec ss; pa_channel_map map; - uint32_t periods, fragsize; + uint32_t nfrags, frag_size; snd_pcm_uframes_t period_size; size_t frame_size; snd_pcm_info_t *pcm_info = NULL; @@ -412,48 +720,107 @@ int pa__init(pa_core *c, pa_module*m) { const char *name; char *name_buf = NULL; int namereg_fail; + int use_mmap = 1, b; + + snd_pcm_info_alloca(&pcm_info); + + pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { - pa_log("failed to parse module arguments"); + pa_log("Failed to parse module arguments"); goto fail; } - ss = c->default_sample_spec; + ss = m->core->default_sample_spec; if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_ALSA) < 0) { - pa_log("failed to parse sample specification and channel map"); + pa_log("Failed to parse sample specification and channel map"); goto fail; } frame_size = pa_frame_size(&ss); - /* Fix latency to 100ms */ - periods = 8; - fragsize = pa_bytes_per_second(&ss)/128; + nfrags = m->core->default_n_fragments; + frag_size = pa_usec_to_bytes(m->core->default_fragment_size_msec*1000, &ss); + if (frag_size <= 0) + frag_size = frame_size; + + if (pa_modargs_get_value_u32(ma, "fragments", &nfrags) < 0 || pa_modargs_get_value_u32(ma, "fragment_size", &frag_size) < 0) { + pa_log("Failed to parse buffer metrics"); + goto fail; + } + period_size = frag_size/frame_size; - if (pa_modargs_get_value_u32(ma, "fragments", &periods) < 0 || pa_modargs_get_value_u32(ma, "fragment_size", &fragsize) < 0) { - pa_log("failed to parse buffer metrics"); + if (pa_modargs_get_value_boolean(ma, "mmap", &use_mmap) < 0) { + pa_log("Failed to parse mmap argument."); goto fail; } - period_size = fragsize/frame_size; u = pa_xnew0(struct userdata, 1); - m->userdata = u; + u->core = m->core; u->module = m; + m->userdata = u; + u->use_mmap = use_mmap; + u->first = 1; + pa_thread_mq_init(&u->thread_mq, m->core->mainloop); + u->rtpoll = pa_rtpoll_new(); + u->alsa_rtpoll_item = NULL; + pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq); snd_config_update_free_global(); - if ((err = snd_pcm_open(&u->pcm_handle, dev = pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { - pa_log("Error opening PCM device %s: %s", dev, snd_strerror(err)); - goto fail; + + dev = pa_xstrdup(pa_modargs_get_value(ma, "device", DEFAULT_DEVICE)); + + for (;;) { + + if ((err = snd_pcm_open(&u->pcm_handle, dev, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { + pa_log("Error opening PCM device %s: %s", dev, snd_strerror(err)); + pa_xfree(dev); + goto fail; + } + + b = use_mmap; + if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &nfrags, &period_size, &b)) < 0) { + + if (err == -EPERM) { + /* Hmm, some hw is very exotic, so we retry with plughw, if hw didn't work */ + + if (pa_startswith(dev, "hw:")) { + char *d = pa_sprintf_malloc("plughw:%s", dev+3); + pa_log_debug("Opening the device as '%s' didn't work, retrying with '%s'.", dev, d); + pa_xfree(dev); + dev = d; + + snd_pcm_close(u->pcm_handle); + u->pcm_handle = NULL; + continue; + } + } + + pa_log("Failed to set hardware parameters: %s", snd_strerror(err)); + pa_xfree(dev); + goto fail; + } + + break; } - if ((err = snd_pcm_info_malloc(&pcm_info)) < 0 || - (err = snd_pcm_info(u->pcm_handle, pcm_info)) < 0) { + u->device_name = dev; + + if (use_mmap && !b) { + pa_log_info("Device doesn't support mmap(), falling back to UNIX read/write mode."); + u->use_mmap = use_mmap = b; + } + + if (u->use_mmap) + pa_log_info("Successfully enabled mmap() mode."); + + if ((err = snd_pcm_info(u->pcm_handle, pcm_info)) < 0) { pa_log("Error fetching PCM info: %s", snd_strerror(err)); goto fail; } - if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &periods, &period_size)) < 0) { - pa_log("Failed to set hardware parameters: %s", snd_strerror(err)); + if ((err = pa_alsa_set_sw_params(u->pcm_handle)) < 0) { + pa_log("Failed to set software parameters: %s", snd_strerror(err)); goto fail; } @@ -464,15 +831,16 @@ int pa__init(pa_core *c, pa_module*m) { /* Seems ALSA didn't like the channel number, so let's fix the channel map */ pa_channel_map_init_auto(&map, ss.channels, PA_CHANNEL_MAP_ALSA); - if ((err = snd_mixer_open(&u->mixer_handle, 0)) < 0) { - pa_log("Error opening mixer: %s", snd_strerror(err)); - goto fail; - } + if ((err = snd_mixer_open(&u->mixer_handle, 0)) < 0) + pa_log_warn("Error opening mixer: %s", snd_strerror(err)); + else { - if ((pa_alsa_prepare_mixer(u->mixer_handle, dev) < 0) || - !(u->mixer_elem = pa_alsa_find_elem(u->mixer_handle, "PCM", "Master"))) { - snd_mixer_close(u->mixer_handle); - u->mixer_handle = NULL; + if ((pa_alsa_prepare_mixer(u->mixer_handle, dev) < 0) || + !(u->mixer_elem = pa_alsa_find_elem(u->mixer_handle, "Master", "PCM"))) { + + snd_mixer_close(u->mixer_handle); + u->mixer_handle = NULL; + } } if ((name = pa_modargs_get_value(ma, "sink_name", NULL))) @@ -482,114 +850,146 @@ int pa__init(pa_core *c, pa_module*m) { namereg_fail = 0; } - if (!(u->sink = pa_sink_new(c, __FILE__, name, namereg_fail, &ss, &map))) { + u->sink = pa_sink_new(m->core, __FILE__, name, namereg_fail, &ss, &map); + pa_xfree(name_buf); + + if (!u->sink) { pa_log("Failed to create sink object"); goto fail; } - u->sink->is_hardware = 1; - u->sink->get_latency = sink_get_latency_cb; + u->sink->parent.process_msg = sink_process_msg; + u->sink->userdata = u; + + pa_sink_set_module(u->sink, m); + pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); + pa_sink_set_rtpoll(u->sink, u->rtpoll); + pa_sink_set_description(u->sink, t = pa_sprintf_malloc( + "ALSA PCM on %s (%s)%s", + dev, + snd_pcm_info_get_name(pcm_info), + use_mmap ? " via DMA" : "")); + pa_xfree(t); + + u->sink->flags = PA_SINK_HARDWARE|PA_SINK_HW_VOLUME_CTRL|PA_SINK_LATENCY; + + u->frame_size = frame_size; + u->fragment_size = frag_size = period_size * frame_size; + u->nfragments = nfrags; + u->hwbuf_size = u->fragment_size * nfrags; + + pa_log_info("Using %u fragments of size %lu bytes.", nfrags, (long unsigned) u->fragment_size); + + pa_memchunk_reset(&u->memchunk); + if (u->mixer_handle) { - assert(u->mixer_elem); + /* Initialize mixer code */ + + pa_assert(u->mixer_elem); + if (snd_mixer_selem_has_playback_volume(u->mixer_elem)) { int i; - for (i = 0;i < ss.channels;i++) { + for (i = 0; i < ss.channels; i++) if (!snd_mixer_selem_has_playback_channel(u->mixer_elem, i)) break; - } if (i == ss.channels) { - u->sink->get_hw_volume = sink_get_hw_volume_cb; - u->sink->set_hw_volume = sink_set_hw_volume_cb; - snd_mixer_selem_get_playback_volume_range( - u->mixer_elem, &u->hw_volume_min, &u->hw_volume_max); - } + pa_log_debug("ALSA device has separate volumes controls for all %u channels.", ss.channels); + u->sink->get_volume = sink_get_volume_cb; + u->sink->set_volume = sink_set_volume_cb; + snd_mixer_selem_get_playback_volume_range(u->mixer_elem, &u->hw_volume_min, &u->hw_volume_max); + } else + pa_log_info("ALSA device lacks separate volumes controls for all %u channels (%u available), falling back to software volume control.", ss.channels, i+1); } + if (snd_mixer_selem_has_playback_switch(u->mixer_elem)) { - u->sink->get_hw_mute = sink_get_hw_mute_cb; - u->sink->set_hw_mute = sink_set_hw_mute_cb; + u->sink->get_mute = sink_get_mute_cb; + u->sink->set_mute = sink_set_mute_cb; } - } - u->sink->userdata = u; - pa_sink_set_owner(u->sink, m); - pa_sink_set_description(u->sink, t = pa_sprintf_malloc("ALSA PCM on %s (%s)", dev, snd_pcm_info_get_name(pcm_info))); - pa_xfree(t); - - u->pcm_fdl = pa_alsa_fdlist_new(); - assert(u->pcm_fdl); - if (pa_alsa_fdlist_init_pcm(u->pcm_fdl, u->pcm_handle, c->mainloop, fdl_callback, u) < 0) { - pa_log("failed to initialise file descriptor monitoring"); - goto fail; - } - if (u->mixer_handle) { u->mixer_fdl = pa_alsa_fdlist_new(); - assert(u->mixer_fdl); - if (pa_alsa_fdlist_init_mixer(u->mixer_fdl, u->mixer_handle, c->mainloop) < 0) { + + if (pa_alsa_fdlist_set_mixer(u->mixer_fdl, u->mixer_handle, m->core->mainloop) < 0) { pa_log("failed to initialise file descriptor monitoring"); goto fail; } + snd_mixer_elem_set_callback(u->mixer_elem, mixer_callback); snd_mixer_elem_set_callback_private(u->mixer_elem, u); } else u->mixer_fdl = NULL; - u->frame_size = frame_size; - u->fragment_size = period_size * frame_size; - - pa_log_info("using %u fragments of size %lu bytes.", periods, (long unsigned)u->fragment_size); - - u->silence.memblock = pa_memblock_new(c->mempool, u->silence.length = u->fragment_size); - assert(u->silence.memblock); - pa_silence_memblock(u->silence.memblock, &ss); - u->silence.index = 0; - - u->memchunk.memblock = NULL; - u->memchunk.index = u->memchunk.length = 0; - - ret = 0; + if (!(u->thread = pa_thread_new(thread_func, u))) { + pa_log("Failed to create thread."); + goto fail; + } /* Get initial mixer settings */ - if (u->sink->get_hw_volume) - u->sink->get_hw_volume(u->sink); - if (u->sink->get_hw_mute) - u->sink->get_hw_mute(u->sink); + if (u->sink->get_volume) + u->sink->get_volume(u->sink); + if (u->sink->get_mute) + u->sink->get_mute(u->sink); -finish: - - pa_xfree(name_buf); + pa_sink_put(u->sink); - if (ma) - pa_modargs_free(ma); + pa_modargs_free(ma); - if (pcm_info) - snd_pcm_info_free(pcm_info); - - return ret; + return 0; fail: - if (u) - pa__done(c, m); + if (ma) + pa_modargs_free(ma); - goto finish; + pa__done(m); + + return -1; } -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module*m) { struct userdata *u; - assert(c && m); + + pa_assert(m); if (!(u = m->userdata)) return; - clear_up(u); + if (u->sink) + pa_sink_unlink(u->sink); + + if (u->thread) { + pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); + pa_thread_free(u->thread); + } + + pa_thread_mq_done(&u->thread_mq); + + if (u->sink) + pa_sink_unref(u->sink); if (u->memchunk.memblock) pa_memblock_unref(u->memchunk.memblock); - if (u->silence.memblock) - pa_memblock_unref(u->silence.memblock); + if (u->alsa_rtpoll_item) + pa_rtpoll_item_free(u->alsa_rtpoll_item); + + if (u->rtpoll) + pa_rtpoll_free(u->rtpoll); + + if (u->mixer_fdl) + pa_alsa_fdlist_free(u->mixer_fdl); + + if (u->mixer_handle) + snd_mixer_close(u->mixer_handle); + + if (u->pcm_handle) { + snd_pcm_drop(u->pcm_handle); + snd_pcm_close(u->pcm_handle); + } + + pa_xfree(u->device_name); pa_xfree(u); -} + snd_config_update_free_global(); +} diff --git a/src/modules/module-alsa-source.c b/src/modules/module-alsa-source.c index 4061d668..d840cac3 100644 --- a/src/modules/module-alsa-source.c +++ b/src/modules/module-alsa-source.c @@ -26,18 +26,12 @@ #include <config.h> #endif -#include <assert.h> #include <stdio.h> -#ifdef HAVE_SYS_POLL_H -#include <sys/poll.h> -#else -#include "poll.h" -#endif - #include <asoundlib.h> #include <pulse/xmalloc.h> +#include <pulse/util.h> #include <pulsecore/core-error.h> #include <pulsecore/core.h> @@ -48,6 +42,11 @@ #include <pulsecore/core-util.h> #include <pulsecore/sample-util.h> #include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/thread.h> +#include <pulsecore/core-error.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/rtpoll.h> #include "alsa-util.h" #include "module-alsa-source-symdef.h" @@ -63,20 +62,35 @@ PA_MODULE_USAGE( "rate=<sample rate> " "fragments=<number of fragments> " "fragment_size=<fragment size> " - "channel_map=<channel map>") + "channel_map=<channel map> " + "mmap=<enable memory mapping?>") + +#define DEFAULT_DEVICE "default" struct userdata { + pa_core *core; + pa_module *module; + pa_source *source; + + pa_thread *thread; + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; + snd_pcm_t *pcm_handle; + + pa_alsa_fdlist *mixer_fdl; snd_mixer_t *mixer_handle; snd_mixer_elem_t *mixer_elem; - pa_source *source; - struct pa_alsa_fdlist *pcm_fdl; - struct pa_alsa_fdlist *mixer_fdl; long hw_volume_max, hw_volume_min; - size_t frame_size, fragment_size; - pa_memchunk memchunk; - pa_module *module; + size_t frame_size, fragment_size, hwbuf_size; + unsigned nfragments; + + char *device_name; + + int use_mmap; + + pa_rtpoll_item *alsa_rtpoll_item; }; static const char* const valid_modargs[] = { @@ -88,257 +102,438 @@ static const char* const valid_modargs[] = { "fragments", "fragment_size", "channel_map", + "mmap", NULL }; -#define DEFAULT_DEVICE "default" +static int mmap_read(struct userdata *u) { + int work_done = 0; -static void update_usage(struct userdata *u) { - pa_module_set_used(u->module, u->source ? pa_source_used_by(u->source) : 0); -} + pa_assert(u); + pa_source_assert_ref(u->source); -static void clear_up(struct userdata *u) { - assert(u); + for (;;) { + snd_pcm_sframes_t n; + int err; + const snd_pcm_channel_area_t *areas; + snd_pcm_uframes_t offset, frames; + pa_memchunk chunk; + void *p; - if (u->source) { - pa_source_disconnect(u->source); - pa_source_unref(u->source); - u->source = NULL; - } + if ((n = snd_pcm_avail_update(u->pcm_handle)) < 0) { - if (u->pcm_fdl) - pa_alsa_fdlist_free(u->pcm_fdl); - if (u->mixer_fdl) - pa_alsa_fdlist_free(u->mixer_fdl); + if (n == -EPIPE) + pa_log_debug("snd_pcm_avail_update: Buffer underrun!"); - u->pcm_fdl = u->mixer_fdl = NULL; + if ((err = snd_pcm_recover(u->pcm_handle, n, 1)) == 0) + continue; - if (u->mixer_handle) { - snd_mixer_close(u->mixer_handle); - u->mixer_handle = NULL; - } + if (err == -EAGAIN) + return work_done; - if (u->pcm_handle) { - snd_pcm_drop(u->pcm_handle); - snd_pcm_close(u->pcm_handle); - u->pcm_handle = NULL; - } -} + pa_log("snd_pcm_avail_update: %s", snd_strerror(err)); + return -1; + } -static int xrun_recovery(struct userdata *u) { - int ret; - assert(u); +/* pa_log("Got request for %i samples", (int) n); */ - pa_log_info("*** ALSA-XRUN (capture) ***"); + if (n <= 0) + return work_done; - if ((ret = snd_pcm_prepare(u->pcm_handle)) < 0) { - pa_log("snd_pcm_prepare() failed: %s", snd_strerror(-ret)); + frames = n; - clear_up(u); - pa_module_unload_request(u->module); + if ((err = snd_pcm_mmap_begin(u->pcm_handle, &areas, &offset, &frames)) < 0) { - return -1; - } + if (err == -EPIPE) + pa_log_debug("snd_pcm_mmap_begin: Buffer underrun!"); - return 0; -} + if ((err = snd_pcm_recover(u->pcm_handle, err, 1)) == 0) + continue; + if (err == -EAGAIN) + return work_done; -static int suspend_recovery(struct userdata *u) { - int ret; - assert(u); + pa_log("Failed to write data to DSP: %s", snd_strerror(err)); + return -1; + } - pa_log_info("*** ALSA-SUSPEND (capture) ***"); + /* Check these are multiples of 8 bit */ + pa_assert((areas[0].first & 7) == 0); + pa_assert((areas[0].step & 7)== 0); - if ((ret = snd_pcm_resume(u->pcm_handle)) < 0) { - if (ret == -EAGAIN) - return -1; + /* We assume a single interleaved memory buffer */ + pa_assert((areas[0].first >> 3) == 0); + pa_assert((areas[0].step >> 3) == u->frame_size); - if (ret != -ENOSYS) - pa_log("snd_pcm_resume() failed: %s", snd_strerror(-ret)); - else { - if ((ret = snd_pcm_prepare(u->pcm_handle)) < 0) - pa_log("snd_pcm_prepare() failed: %s", snd_strerror(-ret)); - } + p = (uint8_t*) areas[0].addr + (offset * u->frame_size); + + chunk.memblock = pa_memblock_new_fixed(u->core->mempool, p, frames * u->frame_size, 1); + chunk.length = pa_memblock_get_length(chunk.memblock); + chunk.index = 0; + + pa_source_post(u->source, &chunk); + + /* FIXME: Maybe we can do something to keep this memory block + * a little bit longer around? */ + pa_memblock_unref_fixed(chunk.memblock); + + if ((err = snd_pcm_mmap_commit(u->pcm_handle, offset, frames)) < 0) { + + if (err == -EPIPE) + pa_log_debug("snd_pcm_mmap_commit: Buffer underrun!"); - if (ret < 0) { - clear_up(u); - pa_module_unload_request(u->module); + if ((err = snd_pcm_recover(u->pcm_handle, err, 1)) == 0) + continue; + + if (err == -EAGAIN) + return work_done; + + pa_log("Failed to write data to DSP: %s", snd_strerror(err)); return -1; } - } - return ret; + work_done = 1; + +/* pa_log("wrote %i samples", (int) frames); */ + } } -static void do_read(struct userdata *u) { - assert(u); +static int unix_read(struct userdata *u) { + snd_pcm_status_t *status; + int work_done = 0; - update_usage(u); + snd_pcm_status_alloca(&status); - for (;;) { - pa_memchunk post_memchunk; - snd_pcm_sframes_t frames; - size_t l; + pa_assert(u); + pa_source_assert_ref(u->source); - if (!u->memchunk.memblock) { - u->memchunk.memblock = pa_memblock_new(u->source->core->mempool, u->memchunk.length = u->fragment_size); - u->memchunk.index = 0; + for (;;) { + void *p; + snd_pcm_sframes_t t, k; + ssize_t l; + int err; + pa_memchunk chunk; + + if ((err = snd_pcm_status(u->pcm_handle, status)) < 0) { + pa_log("Failed to query DSP status data: %s", snd_strerror(err)); + return -1; } - assert(u->memchunk.memblock); - assert(u->memchunk.length); - assert(u->memchunk.memblock->data); - assert(u->memchunk.memblock->length); - assert(u->memchunk.length % u->frame_size == 0); + if (snd_pcm_status_get_avail_max(status)*u->frame_size >= u->hwbuf_size) + pa_log_debug("Buffer overrun!"); - if ((frames = snd_pcm_readi(u->pcm_handle, (uint8_t*) u->memchunk.memblock->data + u->memchunk.index, u->memchunk.length / u->frame_size)) < 0) { - if (frames == -EAGAIN) - return; + l = snd_pcm_status_get_avail(status) * u->frame_size; - if (frames == -EPIPE) { - if (xrun_recovery(u) < 0) - return; + if (l <= 0) + return work_done; - continue; - } + chunk.memblock = pa_memblock_new(u->core->mempool, (size_t) -1); + + k = pa_memblock_get_length(chunk.memblock); + + if (k > l) + k = l; + + k = (k/u->frame_size)*u->frame_size; - if (frames == -ESTRPIPE) { - if (suspend_recovery(u) < 0) - return; + p = pa_memblock_acquire(chunk.memblock); + t = snd_pcm_readi(u->pcm_handle, (uint8_t*) p, k / u->frame_size); + pa_memblock_release(chunk.memblock); +/* pa_log("wrote %i bytes of %u (%u)", t*u->frame_size, u->memchunk.length, l); */ + + pa_assert(t != 0); + + if (t < 0) { + pa_memblock_unref(chunk.memblock); + + if ((t = snd_pcm_recover(u->pcm_handle, t, 1)) == 0) continue; + + if (t == -EAGAIN) { + pa_log_debug("EAGAIN"); + return work_done; + } else { + pa_log("Failed to read data from DSP: %s", snd_strerror(t)); + return -1; } + } - pa_log("snd_pcm_readi() failed: %s", snd_strerror(-frames)); + chunk.index = 0; + chunk.length = t * u->frame_size; - clear_up(u); - pa_module_unload_request(u->module); - return; - } + pa_source_post(u->source, &chunk); + pa_memblock_unref(chunk.memblock); - l = frames * u->frame_size; + work_done = 1; - post_memchunk = u->memchunk; - post_memchunk.length = l; + if (t * u->frame_size >= (unsigned) l) + return work_done; + } +} - pa_source_post(u->source, &post_memchunk); +static pa_usec_t source_get_latency(struct userdata *u) { + pa_usec_t r = 0; + snd_pcm_status_t *status; + snd_pcm_sframes_t frames = 0; + int err; - u->memchunk.index += l; - u->memchunk.length -= l; + snd_pcm_status_alloca(&status); - if (u->memchunk.length == 0) { - pa_memblock_unref(u->memchunk.memblock); - u->memchunk.memblock = NULL; - u->memchunk.index = u->memchunk.length = 0; - } + pa_assert(u); + pa_assert(u->pcm_handle); - break; + if ((err = snd_pcm_status(u->pcm_handle, status)) < 0) + pa_log("Failed to get delay: %s", snd_strerror(err)); + else + frames = snd_pcm_status_get_delay(status); + + if (frames > 0) + r = pa_bytes_to_usec(frames * u->frame_size, &u->source->sample_spec); + + return r; +} + +static int build_pollfd(struct userdata *u) { + int err; + struct pollfd *pollfd; + int n; + + pa_assert(u); + pa_assert(u->pcm_handle); + + if ((n = snd_pcm_poll_descriptors_count(u->pcm_handle)) < 0) { + pa_log("snd_pcm_poll_descriptors_count() failed: %s", snd_strerror(n)); + return -1; } + + if (u->alsa_rtpoll_item) + pa_rtpoll_item_free(u->alsa_rtpoll_item); + + u->alsa_rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, n); + pollfd = pa_rtpoll_item_get_pollfd(u->alsa_rtpoll_item, NULL); + + if ((err = snd_pcm_poll_descriptors(u->pcm_handle, pollfd, n)) < 0) { + pa_log("snd_pcm_poll_descriptors() failed: %s", snd_strerror(err)); + return -1; + } + + return 0; } -static void fdl_callback(void *userdata) { - struct userdata *u = userdata; - assert(u); +static int suspend(struct userdata *u) { + pa_assert(u); + pa_assert(u->pcm_handle); - if (snd_pcm_state(u->pcm_handle) == SND_PCM_STATE_XRUN) - if (xrun_recovery(u) < 0) - return; + /* Let's suspend */ + snd_pcm_close(u->pcm_handle); + u->pcm_handle = NULL; - if (snd_pcm_state(u->pcm_handle) == SND_PCM_STATE_SUSPENDED) - if (suspend_recovery(u) < 0) - return; + if (u->alsa_rtpoll_item) { + pa_rtpoll_item_free(u->alsa_rtpoll_item); + u->alsa_rtpoll_item = NULL; + } - do_read(u); + pa_log_info("Device suspended..."); + + return 0; } -static int mixer_callback(snd_mixer_elem_t *elem, unsigned int mask) { - struct userdata *u = snd_mixer_elem_get_callback_private(elem); +static int unsuspend(struct userdata *u) { + pa_sample_spec ss; + int err, b; + unsigned nfrags; + snd_pcm_uframes_t period_size; - assert(u && u->mixer_handle); + pa_assert(u); + pa_assert(!u->pcm_handle); - if (mask == SND_CTL_EVENT_MASK_REMOVE) - return 0; + pa_log_info("Trying resume..."); - if (mask & SND_CTL_EVENT_MASK_VALUE) { - if (u->source->get_hw_volume) - u->source->get_hw_volume(u->source); - if (u->source->get_hw_mute) - u->source->get_hw_mute(u->source); + snd_config_update_free_global(); + if ((err = snd_pcm_open(&u->pcm_handle, u->device_name, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) < 0) { + pa_log("Error opening PCM device %s: %s", u->device_name, snd_strerror(err)); + goto fail; + } - pa_subscription_post(u->source->core, - PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, - u->source->index); + ss = u->source->sample_spec; + nfrags = u->nfragments; + period_size = u->fragment_size / u->frame_size; + b = u->use_mmap; + + if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &nfrags, &period_size, &b)) < 0) { + pa_log("Failed to set hardware parameters: %s", snd_strerror(err)); + goto fail; } + if (b != u->use_mmap) { + pa_log_warn("Resume failed, couldn't get original access mode."); + goto fail; + } + + if (!pa_sample_spec_equal(&ss, &u->source->sample_spec)) { + pa_log_warn("Resume failed, couldn't restore original sample settings."); + goto fail; + } + + if (nfrags != u->nfragments || period_size*u->frame_size != u->fragment_size) { + pa_log_warn("Resume failed, couldn't restore original fragment settings."); + goto fail; + } + + if ((err = pa_alsa_set_sw_params(u->pcm_handle)) < 0) { + pa_log("Failed to set software parameters: %s", snd_strerror(err)); + goto fail; + } + + if (build_pollfd(u) < 0) + goto fail; + + snd_pcm_start(u->pcm_handle); + + /* FIXME: We need to reload the volume somehow */ + + pa_log_info("Resumed successfully..."); + return 0; + +fail: + if (u->pcm_handle) { + snd_pcm_close(u->pcm_handle); + u->pcm_handle = NULL; + } + + return -1; } -static pa_usec_t source_get_latency_cb(pa_source *s) { - struct userdata *u = s->userdata; - snd_pcm_sframes_t frames; - assert(s && u && u->source); +static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SOURCE(o)->userdata; + + switch (code) { + + case PA_SOURCE_MESSAGE_GET_LATENCY: { + pa_usec_t r = 0; + + if (u->pcm_handle) + r = source_get_latency(u); + + *((pa_usec_t*) data) = r; + + return 0; + } + + case PA_SOURCE_MESSAGE_SET_STATE: + + switch ((pa_source_state_t) PA_PTR_TO_UINT(data)) { + + case PA_SOURCE_SUSPENDED: + pa_assert(PA_SOURCE_OPENED(u->source->thread_info.state)); + + if (suspend(u) < 0) + return -1; + + break; + + case PA_SOURCE_IDLE: + case PA_SOURCE_RUNNING: + + if (u->source->thread_info.state == PA_SOURCE_INIT) { + if (build_pollfd(u) < 0) + return -1; + + snd_pcm_start(u->pcm_handle); + } + + if (u->source->thread_info.state == PA_SOURCE_SUSPENDED) { + if (unsuspend(u) < 0) + return -1; + } + + break; - if (snd_pcm_delay(u->pcm_handle, &frames) < 0) { - pa_log("failed to get delay"); - s->get_latency = NULL; + case PA_SOURCE_UNLINKED: + case PA_SOURCE_INIT: + ; + } + + break; + } + + return pa_source_process_msg(o, code, data, offset, chunk); +} + +static int mixer_callback(snd_mixer_elem_t *elem, unsigned int mask) { + struct userdata *u = snd_mixer_elem_get_callback_private(elem); + + pa_assert(u); + pa_assert(u->mixer_handle); + + if (mask == SND_CTL_EVENT_MASK_REMOVE) return 0; + + if (mask & SND_CTL_EVENT_MASK_VALUE) { + pa_source_get_volume(u->source); + pa_source_get_mute(u->source); } - return pa_bytes_to_usec(frames * u->frame_size, &s->sample_spec); + return 0; } -static int source_get_hw_volume_cb(pa_source *s) { +static int source_get_volume_cb(pa_source *s) { struct userdata *u = s->userdata; - long vol; int err; int i; - assert(u && u->mixer_elem); + pa_assert(u); + pa_assert(u->mixer_elem); - for (i = 0;i < s->hw_volume.channels;i++) { - long set_vol; + for (i = 0; i < s->sample_spec.channels; i++) { + long set_vol, vol; - assert(snd_mixer_selem_has_capture_channel(u->mixer_elem, i)); + pa_assert(snd_mixer_selem_has_capture_channel(u->mixer_elem, i)); if ((err = snd_mixer_selem_get_capture_volume(u->mixer_elem, i, &vol)) < 0) goto fail; - set_vol = (long) roundf(((float) s->hw_volume.values[i] * (u->hw_volume_max - u->hw_volume_min)) / PA_VOLUME_NORM) + u->hw_volume_min; + set_vol = (long) roundf(((float) s->volume.values[i] * (u->hw_volume_max - u->hw_volume_min)) / PA_VOLUME_NORM) + u->hw_volume_min; /* Try to avoid superfluous volume changes */ if (set_vol != vol) - s->hw_volume.values[i] = (pa_volume_t) roundf(((float) (vol - u->hw_volume_min) * PA_VOLUME_NORM) / (u->hw_volume_max - u->hw_volume_min)); + s->volume.values[i] = (pa_volume_t) roundf(((float) (vol - u->hw_volume_min) * PA_VOLUME_NORM) / (u->hw_volume_max - u->hw_volume_min)); } return 0; fail: pa_log_error("Unable to read volume: %s", snd_strerror(err)); - s->get_hw_volume = NULL; - s->set_hw_volume = NULL; + + s->get_volume = NULL; + s->set_volume = NULL; return -1; } -static int source_set_hw_volume_cb(pa_source *s) { +static int source_set_volume_cb(pa_source *s) { struct userdata *u = s->userdata; int err; - pa_volume_t vol; int i; - assert(u && u->mixer_elem); + pa_assert(u); + pa_assert(u->mixer_elem); - for (i = 0;i < s->hw_volume.channels;i++) { - assert(snd_mixer_selem_has_capture_channel(u->mixer_elem, i)); + for (i = 0; i < s->sample_spec.channels; i++) { + long alsa_vol; + pa_volume_t vol; - vol = s->hw_volume.values[i]; + pa_assert(snd_mixer_selem_has_capture_channel(u->mixer_elem, i)); + + vol = s->volume.values[i]; if (vol > PA_VOLUME_NORM) vol = PA_VOLUME_NORM; - vol = (long) roundf(((float) vol * (u->hw_volume_max - u->hw_volume_min)) / PA_VOLUME_NORM) + u->hw_volume_min; + alsa_vol = (long) roundf(((float) vol * (u->hw_volume_max - u->hw_volume_min)) / PA_VOLUME_NORM) + u->hw_volume_min; - if ((err = snd_mixer_selem_set_capture_volume(u->mixer_elem, i, vol)) < 0) + if ((err = snd_mixer_selem_set_capture_volume(u->mixer_elem, i, alsa_vol)) < 0) goto fail; } @@ -346,55 +541,159 @@ static int source_set_hw_volume_cb(pa_source *s) { fail: pa_log_error("Unable to set volume: %s", snd_strerror(err)); - s->get_hw_volume = NULL; - s->set_hw_volume = NULL; + + s->get_volume = NULL; + s->set_volume = NULL; return -1; } -static int source_get_hw_mute_cb(pa_source *s) { +static int source_get_mute_cb(pa_source *s) { struct userdata *u = s->userdata; int err, sw; - assert(u && u->mixer_elem); + pa_assert(u); + pa_assert(u->mixer_elem); - err = snd_mixer_selem_get_capture_switch(u->mixer_elem, 0, &sw); - if (err) { + if ((err = snd_mixer_selem_get_capture_switch(u->mixer_elem, 0, &sw)) < 0) { pa_log_error("Unable to get switch: %s", snd_strerror(err)); - s->get_hw_mute = NULL; - s->set_hw_mute = NULL; + + s->get_mute = NULL; + s->set_mute = NULL; return -1; } - s->hw_muted = !sw; + s->muted = !sw; return 0; } -static int source_set_hw_mute_cb(pa_source *s) { +static int source_set_mute_cb(pa_source *s) { struct userdata *u = s->userdata; int err; - assert(u && u->mixer_elem); + pa_assert(u); + pa_assert(u->mixer_elem); - err = snd_mixer_selem_set_capture_switch_all(u->mixer_elem, !s->hw_muted); - if (err) { + if ((err = snd_mixer_selem_set_capture_switch_all(u->mixer_elem, !s->muted)) < 0) { pa_log_error("Unable to set switch: %s", snd_strerror(err)); - s->get_hw_mute = NULL; - s->set_hw_mute = NULL; + + s->get_mute = NULL; + s->set_mute = NULL; return -1; } return 0; } -int pa__init(pa_core *c, pa_module*m) { +static void thread_func(void *userdata) { + struct userdata *u = userdata; + + pa_assert(u); + + pa_log_debug("Thread starting up"); + + if (u->core->high_priority) + pa_make_realtime(); + + pa_thread_mq_install(&u->thread_mq); + pa_rtpoll_install(u->rtpoll); + + for (;;) { + int ret; + + /* Read some data and pass it to the sources */ + if (PA_SOURCE_OPENED(u->source->thread_info.state)) { + + if (u->use_mmap) { + if (mmap_read(u) < 0) + goto fail; + + } else { + if (unix_read(u) < 0) + goto fail; + } + } + + /* Hmm, nothing to do. Let's sleep */ + if ((ret = pa_rtpoll_run(u->rtpoll, 1)) < 0) + goto fail; + + if (ret == 0) + goto finish; + + /* Tell ALSA about this and process its response */ + if (PA_SOURCE_OPENED(u->source->thread_info.state)) { + struct pollfd *pollfd; + unsigned short revents = 0; + int err; + unsigned n; + + pollfd = pa_rtpoll_item_get_pollfd(u->alsa_rtpoll_item, &n); + + if ((err = snd_pcm_poll_descriptors_revents(u->pcm_handle, pollfd, n, &revents)) < 0) { + pa_log("snd_pcm_poll_descriptors_revents() failed: %s", snd_strerror(err)); + goto fail; + } + + if (revents & (POLLERR|POLLNVAL|POLLHUP)) { + + if (revents & POLLERR) + pa_log_warn("Got POLLERR from ALSA"); + if (revents & POLLNVAL) + pa_log_warn("Got POLLNVAL from ALSA"); + if (revents & POLLHUP) + pa_log_warn("Got POLLHUP from ALSA"); + + /* Try to recover from this error */ + + switch (snd_pcm_state(u->pcm_handle)) { + + case SND_PCM_STATE_XRUN: + if ((err = snd_pcm_recover(u->pcm_handle, -EPIPE, 1)) != 0) { + pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP and XRUN: %s", snd_strerror(err)); + goto fail; + } + break; + + case SND_PCM_STATE_SUSPENDED: + if ((err = snd_pcm_recover(u->pcm_handle, -ESTRPIPE, 1)) != 0) { + pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP and SUSPENDED: %s", snd_strerror(err)); + goto fail; + } + break; + + default: + + snd_pcm_drop(u->pcm_handle); + + if ((err = snd_pcm_prepare(u->pcm_handle)) < 0) { + pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP with snd_pcm_prepare(): %s", snd_strerror(err)); + goto fail; + } + break; + } + } + } + } + +fail: + /* If this was no regular exit from the loop we have to continue + * processing messages until we received PA_MESSAGE_SHUTDOWN */ + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + +finish: + pa_log_debug("Thread shutting down"); +} + +int pa__init(pa_module*m) { + pa_modargs *ma = NULL; - int ret = -1; struct userdata *u = NULL; - const char *dev; + char *dev; pa_sample_spec ss; pa_channel_map map; - unsigned periods, fragsize; + unsigned nfrags, frag_size; snd_pcm_uframes_t period_size; size_t frame_size; snd_pcm_info_t *pcm_info = NULL; @@ -403,64 +702,125 @@ int pa__init(pa_core *c, pa_module*m) { const char *name; char *name_buf = NULL; int namereg_fail; + int use_mmap = 1, b; + + snd_pcm_info_alloca(&pcm_info); + + pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { - pa_log("failed to parse module arguments"); + pa_log("Failed to parse module arguments"); goto fail; } - ss = c->default_sample_spec; + ss = m->core->default_sample_spec; if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_ALSA) < 0) { - pa_log("failed to parse sample specification"); + pa_log("Failed to parse sample specification"); goto fail; } frame_size = pa_frame_size(&ss); - /* Fix latency to 100ms */ - periods = 12; - fragsize = pa_bytes_per_second(&ss)/128; + nfrags = m->core->default_n_fragments; + frag_size = pa_usec_to_bytes(m->core->default_fragment_size_msec*1000, &ss); + if (frag_size <= 0) + frag_size = frame_size; + + if (pa_modargs_get_value_u32(ma, "fragments", &nfrags) < 0 || pa_modargs_get_value_u32(ma, "fragment_size", &frag_size) < 0) { + pa_log("Failed to parse buffer metrics"); + goto fail; + } + period_size = frag_size/frame_size; - if (pa_modargs_get_value_u32(ma, "fragments", &periods) < 0 || pa_modargs_get_value_u32(ma, "fragment_size", &fragsize) < 0) { - pa_log("failed to parse buffer metrics"); + if (pa_modargs_get_value_boolean(ma, "mmap", &use_mmap) < 0) { + pa_log("Failed to parse mmap argument."); goto fail; } - period_size = fragsize/frame_size; u = pa_xnew0(struct userdata, 1); - m->userdata = u; + u->core = m->core; u->module = m; + m->userdata = u; + u->use_mmap = use_mmap; + pa_thread_mq_init(&u->thread_mq, m->core->mainloop); + u->rtpoll = pa_rtpoll_new(); + u->alsa_rtpoll_item = NULL; + pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq); snd_config_update_free_global(); - if ((err = snd_pcm_open(&u->pcm_handle, dev = pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) < 0) { - pa_log("Error opening PCM device %s: %s", dev, snd_strerror(err)); - goto fail; + + dev = pa_xstrdup(pa_modargs_get_value(ma, "device", DEFAULT_DEVICE)); + + for (;;) { + + if ((err = snd_pcm_open(&u->pcm_handle, dev, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) < 0) { + pa_log("Error opening PCM device %s: %s", dev, snd_strerror(err)); + pa_xfree(dev); + goto fail; + } + + b = use_mmap; + if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &nfrags, &period_size, &b)) < 0) { + + if (err == -EPERM) { + /* Hmm, some hw is very exotic, so we retry with plughw, if hw didn't work */ + + if (pa_startswith(dev, "hw:")) { + char *d = pa_sprintf_malloc("plughw:%s", dev+3); + pa_log_debug("Opening the device as '%s' didn't work, retrying with '%s'.", dev, d); + pa_xfree(dev); + dev = d; + + snd_pcm_close(u->pcm_handle); + u->pcm_handle = NULL; + continue; + } + } + + pa_log("Failed to set hardware parameters: %s", snd_strerror(err)); + pa_xfree(dev); + goto fail; + } + + break; } - if ((err = snd_pcm_info_malloc(&pcm_info)) < 0 || - (err = snd_pcm_info(u->pcm_handle, pcm_info)) < 0) { + u->device_name = dev; + + if (use_mmap && !b) { + pa_log_info("Device doesn't support mmap(), falling back to UNIX read/write mode."); + u->use_mmap = use_mmap = b; + } + + if (u->use_mmap) + pa_log_info("Successfully enabled mmap() mode."); + + if ((err = snd_pcm_info(u->pcm_handle, pcm_info)) < 0) { pa_log("Error fetching PCM info: %s", snd_strerror(err)); goto fail; } - if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &periods, &period_size)) < 0) { - pa_log("Failed to set hardware parameters: %s", snd_strerror(err)); + if ((err = pa_alsa_set_sw_params(u->pcm_handle)) < 0) { + pa_log("Failed to set software parameters: %s", snd_strerror(err)); goto fail; } + /* ALSA might tweak the sample spec, so recalculate the frame size */ + frame_size = pa_frame_size(&ss); + if (ss.channels != map.channels) /* Seems ALSA didn't like the channel number, so let's fix the channel map */ pa_channel_map_init_auto(&map, ss.channels, PA_CHANNEL_MAP_ALSA); - if ((err = snd_mixer_open(&u->mixer_handle, 0)) < 0) { + if ((err = snd_mixer_open(&u->mixer_handle, 0)) < 0) pa_log("Error opening mixer: %s", snd_strerror(err)); - goto fail; - } + else { - if ((pa_alsa_prepare_mixer(u->mixer_handle, dev) < 0) || - !(u->mixer_elem = pa_alsa_find_elem(u->mixer_handle, "Capture", "Mic"))) { - snd_mixer_close(u->mixer_handle); - u->mixer_handle = NULL; + if ((pa_alsa_prepare_mixer(u->mixer_handle, dev) < 0) || + !(u->mixer_elem = pa_alsa_find_elem(u->mixer_handle, "Capture", NULL))) { + snd_mixer_close(u->mixer_handle); + u->mixer_handle = NULL; + } } if ((name = pa_modargs_get_value(ma, "source_name", NULL))) @@ -470,16 +830,39 @@ int pa__init(pa_core *c, pa_module*m) { namereg_fail = 0; } - if (!(u->source = pa_source_new(c, __FILE__, name, namereg_fail, &ss, &map))) { + u->source = pa_source_new(m->core, __FILE__, name, namereg_fail, &ss, &map); + pa_xfree(name_buf); + + if (!u->source) { pa_log("Failed to create source object"); goto fail; } - u->source->is_hardware = 1; + u->source->parent.process_msg = source_process_msg; u->source->userdata = u; - u->source->get_latency = source_get_latency_cb; + + pa_source_set_module(u->source, m); + pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); + pa_source_set_rtpoll(u->source, u->rtpoll); + pa_source_set_description(u->source, t = pa_sprintf_malloc( + "ALSA PCM on %s (%s)%s", + dev, + snd_pcm_info_get_name(pcm_info), + use_mmap ? " via DMA" : "")); + pa_xfree(t); + + u->source->flags = PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY|PA_SOURCE_HW_VOLUME_CTRL; + + u->frame_size = frame_size; + u->fragment_size = frag_size = period_size * frame_size; + u->nfragments = nfrags; + u->hwbuf_size = u->fragment_size * nfrags; + + pa_log_info("Using %u fragments of size %lu bytes.", nfrags, (long unsigned) u->fragment_size); + if (u->mixer_handle) { - assert(u->mixer_elem); + pa_assert(u->mixer_elem); + if (snd_mixer_selem_has_capture_volume(u->mixer_elem)) { int i; @@ -489,89 +872,95 @@ int pa__init(pa_core *c, pa_module*m) { } if (i == ss.channels) { - u->source->get_hw_volume = source_get_hw_volume_cb; - u->source->set_hw_volume = source_set_hw_volume_cb; - snd_mixer_selem_get_capture_volume_range( - u->mixer_elem, &u->hw_volume_min, &u->hw_volume_max); + u->source->get_volume = source_get_volume_cb; + u->source->set_volume = source_set_volume_cb; + snd_mixer_selem_get_capture_volume_range(u->mixer_elem, &u->hw_volume_min, &u->hw_volume_max); } } + if (snd_mixer_selem_has_capture_switch(u->mixer_elem)) { - u->source->get_hw_mute = source_get_hw_mute_cb; - u->source->set_hw_mute = source_set_hw_mute_cb; + u->source->get_mute = source_get_mute_cb; + u->source->set_mute = source_set_mute_cb; } - } - pa_source_set_owner(u->source, m); - pa_source_set_description(u->source, t = pa_sprintf_malloc("ALSA PCM on %s (%s)", dev, snd_pcm_info_get_name(pcm_info))); - pa_xfree(t); - u->pcm_fdl = pa_alsa_fdlist_new(); - assert(u->pcm_fdl); - if (pa_alsa_fdlist_init_pcm(u->pcm_fdl, u->pcm_handle, c->mainloop, fdl_callback, u) < 0) { - pa_log("failed to initialise file descriptor monitoring"); - goto fail; - } - - if (u->mixer_handle) { u->mixer_fdl = pa_alsa_fdlist_new(); - assert(u->mixer_fdl); - if (pa_alsa_fdlist_init_mixer(u->mixer_fdl, u->mixer_handle, c->mainloop) < 0) { + + if (pa_alsa_fdlist_set_mixer(u->mixer_fdl, u->mixer_handle, m->core->mainloop) < 0) { pa_log("failed to initialise file descriptor monitoring"); goto fail; } + snd_mixer_elem_set_callback(u->mixer_elem, mixer_callback); snd_mixer_elem_set_callback_private(u->mixer_elem, u); } else u->mixer_fdl = NULL; - u->frame_size = frame_size; - u->fragment_size = period_size * frame_size; - - pa_log_info("using %u fragments of size %lu bytes.", periods, (long unsigned) u->fragment_size); - - u->memchunk.memblock = NULL; - u->memchunk.index = u->memchunk.length = 0; - - snd_pcm_start(u->pcm_handle); - - ret = 0; - + if (!(u->thread = pa_thread_new(thread_func, u))) { + pa_log("Failed to create thread."); + goto fail; + } /* Get initial mixer settings */ - if (u->source->get_hw_volume) - u->source->get_hw_volume(u->source); - if (u->source->get_hw_mute) - u->source->get_hw_mute(u->source); + if (u->source->get_volume) + u->source->get_volume(u->source); + if (u->source->get_mute) + u->source->get_mute(u->source); -finish: - pa_xfree(name_buf); - - if (ma) - pa_modargs_free(ma); + pa_source_put(u->source); - if (pcm_info) - snd_pcm_info_free(pcm_info); + pa_modargs_free(ma); - return ret; + return 0; fail: - if (u) - pa__done(c, m); + if (ma) + pa_modargs_free(ma); - goto finish; + pa__done(m); + + return -1; } -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module*m) { struct userdata *u; - assert(c && m); + + pa_assert(m); if (!(u = m->userdata)) return; - clear_up(u); + if (u->source) + pa_source_unlink(u->source); - if (u->memchunk.memblock) - pa_memblock_unref(u->memchunk.memblock); + if (u->thread) { + pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); + pa_thread_free(u->thread); + } + pa_thread_mq_done(&u->thread_mq); + + if (u->source) + pa_source_unref(u->source); + + if (u->alsa_rtpoll_item) + pa_rtpoll_item_free(u->alsa_rtpoll_item); + + if (u->rtpoll) + pa_rtpoll_free(u->rtpoll); + + if (u->mixer_fdl) + pa_alsa_fdlist_free(u->mixer_fdl); + + if (u->mixer_handle) + snd_mixer_close(u->mixer_handle); + + if (u->pcm_handle) { + snd_pcm_drop(u->pcm_handle); + snd_pcm_close(u->pcm_handle); + } + + pa_xfree(u->device_name); pa_xfree(u); -} + snd_config_update_free_global(); +} diff --git a/src/modules/module-cli.c b/src/modules/module-cli.c index 19ac0c26..84125214 100644 --- a/src/modules/module-cli.c +++ b/src/modules/module-cli.c @@ -26,7 +26,6 @@ #endif #include <stdio.h> -#include <assert.h> #include <unistd.h> #include <pulsecore/module.h> @@ -35,6 +34,7 @@ #include <pulsecore/sioman.h> #include <pulsecore/log.h> #include <pulsecore/modargs.h> +#include <pulsecore/macro.h> #include "module-cli-symdef.h" @@ -51,8 +51,8 @@ static const char* const valid_modargs[] = { static void eof_and_unload_cb(pa_cli*c, void *userdata) { pa_module *m = userdata; - assert(c); - assert(m); + pa_assert(c); + pa_assert(m); pa_module_unload_request(m); } @@ -60,21 +60,20 @@ static void eof_and_unload_cb(pa_cli*c, void *userdata) { static void eof_and_exit_cb(pa_cli*c, void *userdata) { pa_module *m = userdata; - assert(c); - assert(m); + pa_assert(c); + pa_assert(m); m->core->mainloop->quit(m->core->mainloop, 0); } -int pa__init(pa_core *c, pa_module*m) { +int pa__init(pa_module*m) { pa_iochannel *io; pa_modargs *ma; int exit_on_eof = 0; - assert(c); - assert(m); + pa_assert(m); - if (c->running_as_daemon) { + if (m->core->running_as_daemon) { pa_log_info("Running as daemon, refusing to load this module."); return 0; } @@ -94,12 +93,10 @@ int pa__init(pa_core *c, pa_module*m) { goto fail; } - io = pa_iochannel_new(c->mainloop, STDIN_FILENO, STDOUT_FILENO); - assert(io); + io = pa_iochannel_new(m->core->mainloop, STDIN_FILENO, STDOUT_FILENO); pa_iochannel_set_noclose(io, 1); - m->userdata = pa_cli_new(c, io, m); - assert(m->userdata); + m->userdata = pa_cli_new(m->core, io, m); pa_cli_set_eof_callback(m->userdata, exit_on_eof ? eof_and_exit_cb : eof_and_unload_cb, m); @@ -115,11 +112,10 @@ fail: return -1; } -void pa__done(pa_core *c, pa_module*m) { - assert(c); - assert(m); +void pa__done(pa_module*m) { + pa_assert(m); - if (c->running_as_daemon == 0) { + if (m->core->running_as_daemon == 0) { pa_cli_free(m->userdata); pa_stdio_release(); } diff --git a/src/modules/module-combine.c b/src/modules/module-combine.c index 716c20b2..665bf9dd 100644 --- a/src/modules/module-combine.c +++ b/src/modules/module-combine.c @@ -25,12 +25,13 @@ #include <config.h> #endif -#include <assert.h> #include <stdio.h> +#include <errno.h> #include <pulse/timeval.h> #include <pulse/xmalloc.h> +#include <pulsecore/macro.h> #include <pulsecore/module.h> #include <pulsecore/llist.h> #include <pulsecore/sink.h> @@ -40,6 +41,12 @@ #include <pulsecore/core-util.h> #include <pulsecore/modargs.h> #include <pulsecore/namereg.h> +#include <pulsecore/mutex.h> +#include <pulsecore/thread.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/rtpoll.h> +#include <pulsecore/rtclock.h> +#include <pulsecore/core-error.h> #include "module-combine-symdef.h" @@ -55,13 +62,12 @@ PA_MODULE_USAGE( "format=<sample format> " "channels=<number of channels> " "rate=<sample rate> " - "channel_map=<channel map> ") + "channel_map=<channel map>") #define DEFAULT_SINK_NAME "combined" #define MEMBLOCKQ_MAXLENGTH (1024*170) -#define RENDER_SIZE (1024*10) -#define DEFAULT_ADJUST_TIME 20 +#define DEFAULT_ADJUST_TIME 10 static const char* const valid_modargs[] = { "sink_name", @@ -78,170 +84,689 @@ static const char* const valid_modargs[] = { struct output { struct userdata *userdata; + + pa_sink *sink; pa_sink_input *sink_input; - size_t counter; + + pa_asyncmsgq *inq, /* Message queue from the sink thread to this sink input */ + *outq; /* Message queue from this sink input to the sink thread */ + pa_rtpoll_item *inq_rtpoll_item, *outq_rtpoll_item; + pa_memblockq *memblockq; + pa_usec_t total_latency; + PA_LLIST_FIELDS(struct output); }; struct userdata { - pa_module *module; pa_core *core; + pa_module *module; pa_sink *sink; - unsigned n_outputs; - struct output *master; + + pa_thread *thread; + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; + pa_time_event *time_event; uint32_t adjust_time; - PA_LLIST_HEAD(struct output, outputs); + pa_bool_t automatic; + size_t block_size; + + pa_hook_slot *sink_new_slot, *sink_unlink_slot, *sink_state_changed_slot; + + pa_resample_method_t resample_method; + + struct timeval adjust_timestamp; + + struct output *master; + pa_idxset* outputs; /* managed in main context */ + + struct { + PA_LLIST_HEAD(struct output, active_outputs); /* managed in IO thread context */ + pa_atomic_t running; /* we cache that value here, so that every thread can query it cheaply */ + struct timeval timestamp; + pa_bool_t in_null_mode; + } thread_info; }; -static void output_free(struct output *o); -static void clear_up(struct userdata *u); +enum { + SINK_MESSAGE_ADD_OUTPUT = PA_SINK_MESSAGE_MAX, + SINK_MESSAGE_REMOVE_OUTPUT, + SINK_MESSAGE_NEED +}; -static void update_usage(struct userdata *u) { - pa_module_set_used(u->module, u->sink ? pa_sink_used_by(u->sink) : 0); -} +enum { + SINK_INPUT_MESSAGE_POST = PA_SINK_INPUT_MESSAGE_MAX +}; + +static void output_free(struct output *o); +static int output_create_sink_input(struct output *o); +static void update_master(struct userdata *u, struct output *o); +static void pick_master(struct userdata *u, struct output *except); static void adjust_rates(struct userdata *u) { struct output *o; pa_usec_t max_sink_latency = 0, min_total_latency = (pa_usec_t) -1, target_latency; uint32_t base_rate; - assert(u && u->sink); + uint32_t idx; - for (o = u->outputs; o; o = o->next) { - uint32_t sink_latency = o->sink_input->sink ? pa_sink_get_latency(o->sink_input->sink) : 0; + pa_assert(u); + pa_sink_assert_ref(u->sink); + if (pa_idxset_size(u->outputs) <= 0) + return; + + if (!u->master) + return; + + if (!PA_SINK_OPENED(pa_sink_get_state(u->sink))) + return; + + for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) { + pa_usec_t sink_latency; + + if (!o->sink_input || !PA_SINK_OPENED(pa_sink_get_state(o->sink))) + continue; + + sink_latency = pa_sink_get_latency(o->sink); o->total_latency = sink_latency + pa_sink_input_get_latency(o->sink_input); if (sink_latency > max_sink_latency) max_sink_latency = sink_latency; - if (o->total_latency < min_total_latency) + if (min_total_latency == (pa_usec_t) -1 || o->total_latency < min_total_latency) min_total_latency = o->total_latency; } - assert(min_total_latency != (pa_usec_t) -1); + if (min_total_latency == (pa_usec_t) -1) + return; target_latency = max_sink_latency > min_total_latency ? max_sink_latency : min_total_latency; pa_log_info("[%s] target latency is %0.0f usec.", u->sink->name, (float) target_latency); + pa_log_info("[%s] master %s latency %0.0f usec.", u->sink->name, u->master->sink->name, (float) u->master->total_latency); base_rate = u->sink->sample_spec.rate; - for (o = u->outputs; o; o = o->next) { + for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) { uint32_t r = base_rate; + if (!o->sink_input || !PA_SINK_OPENED(pa_sink_get_state(o->sink))) + continue; + if (o->total_latency < target_latency) - r -= (uint32_t) (((((double) target_latency - o->total_latency))/u->adjust_time)*r/ 1000000); + r -= (uint32_t) (((((double) target_latency - o->total_latency))/u->adjust_time)*r/PA_USEC_PER_SEC); else if (o->total_latency > target_latency) - r += (uint32_t) (((((double) o->total_latency - target_latency))/u->adjust_time)*r/ 1000000); + r += (uint32_t) (((((double) o->total_latency - target_latency))/u->adjust_time)*r/PA_USEC_PER_SEC); - if (r < (uint32_t) (base_rate*0.9) || r > (uint32_t) (base_rate*1.1)) + if (r < (uint32_t) (base_rate*0.9) || r > (uint32_t) (base_rate*1.1)) { pa_log_warn("[%s] sample rates too different, not adjusting (%u vs. %u).", o->sink_input->name, base_rate, r); - else { + pa_sink_input_set_rate(o->sink_input, base_rate); + } else { pa_log_info("[%s] new rate is %u Hz; ratio is %0.3f; latency is %0.0f usec.", o->sink_input->name, r, (double) r / base_rate, (float) o->total_latency); pa_sink_input_set_rate(o->sink_input, r); } } } -static void request_memblock(struct userdata *u) { - pa_memchunk chunk; - struct output *o; - assert(u && u->sink); +static void time_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *tv, void *userdata) { + struct userdata *u = userdata; + struct timeval n; + + pa_assert(u); + pa_assert(a); + pa_assert(u->time_event == e); - update_usage(u); + adjust_rates(u); + + pa_gettimeofday(&n); + n.tv_sec += u->adjust_time; + u->sink->core->mainloop->time_restart(e, &n); +} + +static void thread_func(void *userdata) { + struct userdata *u = userdata; - if (pa_sink_render(u->sink, RENDER_SIZE, &chunk) < 0) + pa_assert(u); + + pa_log_debug("Thread starting up"); + + if (u->core->high_priority) + pa_make_realtime(); + + pa_thread_mq_install(&u->thread_mq); + pa_rtpoll_install(u->rtpoll); + + pa_rtclock_get(&u->thread_info.timestamp); + u->thread_info.in_null_mode = FALSE; + + for (;;) { + int ret; + + /* If no outputs are connected, render some data and drop it immediately. */ + if (u->sink->thread_info.state == PA_SINK_RUNNING && !u->thread_info.active_outputs) { + struct timeval now; + + pa_rtclock_get(&now); + + if (!u->thread_info.in_null_mode || pa_timeval_cmp(&u->thread_info.timestamp, &now) <= 0) { + pa_sink_skip(u->sink, u->block_size); + + if (!u->thread_info.in_null_mode) + u->thread_info.timestamp = now; + + pa_timeval_add(&u->thread_info.timestamp, pa_bytes_to_usec(u->block_size, &u->sink->sample_spec)); + } + + pa_rtpoll_set_timer_absolute(u->rtpoll, &u->thread_info.timestamp); + u->thread_info.in_null_mode = TRUE; + + } else { + pa_rtpoll_set_timer_disabled(u->rtpoll); + u->thread_info.in_null_mode = FALSE; + } + + /* Hmm, nothing to do. Let's sleep */ + if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) { + pa_log_info("pa_rtpoll_run() = %i", ret); + goto fail; + } + + if (ret == 0) + goto finish; + } + +fail: + /* If this was no regular exit from the loop we have to continue + * processing messages until we received PA_MESSAGE_SHUTDOWN */ + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + +finish: + pa_log_debug("Thread shutting down"); +} + +/* Called from I/O thread context */ +static void render_memblock(struct userdata *u, struct output *o, size_t length) { + pa_assert(u); + pa_assert(o); + + /* We are run by the sink thread, on behalf of an output (o). The + * other output is waiting for us, hence it is safe to access its + * mainblockq and asyncmsgq directly. */ + + /* If we are not running, we cannot produce any data */ + if (!pa_atomic_load(&u->thread_info.running)) return; - for (o = u->outputs; o; o = o->next) - pa_memblockq_push_align(o->memblockq, &chunk); + /* Maybe there's some data in the requesting output's queue + * now? */ + while (pa_asyncmsgq_process_one(o->inq) > 0) + ; + + /* Ok, now let's prepare some data if we really have to */ + while (!pa_memblockq_is_readable(o->memblockq)) { + struct output *j; + pa_memchunk chunk; + + /* Render data! */ + pa_sink_render(u->sink, length, &chunk); + + /* OK, let's send this data to the other threads */ + for (j = u->thread_info.active_outputs; j; j = j->next) - pa_memblock_unref(chunk.memblock); + /* Send to other outputs, which are not the requesting + * one */ + + if (j != o) + pa_asyncmsgq_post(j->inq, PA_MSGOBJECT(j->sink_input), SINK_INPUT_MESSAGE_POST, NULL, 0, &chunk, NULL); + + /* And place it directly into the requesting output's queue */ + if (o) + pa_memblockq_push_align(o->memblockq, &chunk); + + pa_memblock_unref(chunk.memblock); + } } -static void time_callback(pa_mainloop_api*a, pa_time_event* e, PA_GCC_UNUSED const struct timeval *tv, void *userdata) { - struct userdata *u = userdata; - struct timeval n; - assert(u && a && u->time_event == e); +/* Called from I/O thread context */ +static void request_memblock(struct output *o, size_t length) { + pa_assert(o); + pa_sink_input_assert_ref(o->sink_input); + pa_sink_assert_ref(o->userdata->sink); - adjust_rates(u); + /* If another thread already prepared some data we received + * the data over the asyncmsgq, hence let's first process + * it. */ + while (pa_asyncmsgq_process_one(o->inq) > 0) + ; - pa_gettimeofday(&n); - n.tv_sec += u->adjust_time; - u->sink->core->mainloop->time_restart(e, &n); + /* Check whether we're now readable */ + if (pa_memblockq_is_readable(o->memblockq)) + return; + + /* OK, we need to prepare new data, but only if the sink is actually running */ + if (pa_atomic_load(&o->userdata->thread_info.running)) + pa_asyncmsgq_send(o->outq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_NEED, o, length, NULL); } -static int sink_input_peek_cb(pa_sink_input *i, pa_memchunk *chunk) { - struct output *o = i->userdata; - assert(i && o && o->sink_input && chunk); +/* Called from I/O thread context */ +static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) { + struct output *o; - if (pa_memblockq_peek(o->memblockq, chunk) >= 0) - return 0; + pa_sink_input_assert_ref(i); + pa_assert_se(o = i->userdata); - /* Try harder */ - request_memblock(o->userdata); + /* If necessary, get some new data */ + request_memblock(o, length); return pa_memblockq_peek(o->memblockq, chunk); } -static void sink_input_drop_cb(pa_sink_input *i, const pa_memchunk *chunk, size_t length) { - struct output *o = i->userdata; - assert(i && o && o->sink_input && chunk && length); +/* Called from I/O thread context */ +static void sink_input_drop_cb(pa_sink_input *i, size_t length) { + struct output *o; + + pa_sink_input_assert_ref(i); + pa_assert(length > 0); + pa_assert_se(o = i->userdata); + + pa_memblockq_drop(o->memblockq, length); +} + +/* Called from I/O thread context */ +static void sink_input_attach_cb(pa_sink_input *i) { + struct output *o; + + pa_sink_input_assert_ref(i); + pa_assert_se(o = i->userdata); + + /* Set up the queue from the sink thread to us */ + pa_assert(!o->inq_rtpoll_item); + o->inq_rtpoll_item = pa_rtpoll_item_new_asyncmsgq( + i->sink->rtpoll, + PA_RTPOLL_LATE, /* This one is not that important, since we check for data in _peek() anyway. */ + o->inq); +} + +/* Called from I/O thread context */ +static void sink_input_detach_cb(pa_sink_input *i) { + struct output *o; + + pa_sink_input_assert_ref(i); + pa_assert_se(o = i->userdata); - pa_memblockq_drop(o->memblockq, chunk, length); - o->counter += length; + /* Shut down the queue from the sink thread to us */ + pa_assert(o->inq_rtpoll_item); + pa_rtpoll_item_free(o->inq_rtpoll_item); + o->inq_rtpoll_item = NULL; } +/* Called from main context */ static void sink_input_kill_cb(pa_sink_input *i) { - struct output *o = i->userdata; - assert(i && o && o->sink_input); + struct output *o; + + pa_sink_input_assert_ref(i); + pa_assert(o = i->userdata); + pa_module_unload_request(o->userdata->module); - clear_up(o->userdata); + output_free(o); +} + +/* Called from thread context */ +static int sink_input_process_msg(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct output *o = PA_SINK_INPUT(obj)->userdata; + + switch (code) { + + case PA_SINK_INPUT_MESSAGE_GET_LATENCY: { + pa_usec_t *r = data; + + *r = pa_bytes_to_usec(pa_memblockq_get_length(o->memblockq), &o->sink_input->sample_spec); + + /* Fall through, the default handler will add in the extra + * latency added by the resampler */ + break; + } + + case SINK_INPUT_MESSAGE_POST: + + if (PA_SINK_OPENED(o->sink_input->sink->thread_info.state)) + pa_memblockq_push_align(o->memblockq, chunk); + else + pa_memblockq_flush(o->memblockq); + + break; + } + + return pa_sink_input_process_msg(obj, code, data, offset, chunk); } -static pa_usec_t sink_input_get_latency_cb(pa_sink_input *i) { - struct output *o = i->userdata; - assert(i && o && o->sink_input); +/* Called from main context */ +static void disable_output(struct output *o) { + pa_assert(o); + + if (!o->sink_input) + return; + + pa_asyncmsgq_send(o->userdata->sink->asyncmsgq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_REMOVE_OUTPUT, o, 0, NULL); + pa_sink_input_unlink(o->sink_input); + pa_sink_input_unref(o->sink_input); + o->sink_input = NULL; - return pa_bytes_to_usec(pa_memblockq_get_length(o->memblockq), &i->sample_spec); } -static pa_usec_t sink_get_latency_cb(pa_sink *s) { - struct userdata *u = s->userdata; - assert(s && u && u->sink && u->master); +/* Called from main context */ +static void enable_output(struct output *o) { + pa_assert(o); + + if (o->sink_input) + return; + + if (output_create_sink_input(o) >= 0) { + + pa_memblockq_flush(o->memblockq); + + pa_sink_input_put(o->sink_input); + + if (o->userdata->sink && PA_SINK_LINKED(pa_sink_get_state(o->userdata->sink))) + pa_asyncmsgq_send(o->userdata->sink->asyncmsgq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_ADD_OUTPUT, o, 0, NULL); + } +} + +/* Called from main context */ +static void suspend(struct userdata *u) { + struct output *o; + uint32_t idx; + + pa_assert(u); + + /* Let's suspend by unlinking all streams */ + for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) + disable_output(o); + + pick_master(u, NULL); + + pa_log_info("Device suspended..."); +} + +/* Called from main context */ +static void unsuspend(struct userdata *u) { + struct output *o; + uint32_t idx; + + pa_assert(u); + + /* Let's resume */ + for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) { + + pa_sink_suspend(o->sink, FALSE); + + if (PA_SINK_OPENED(pa_sink_get_state(o->sink))) + enable_output(o); + } + + pick_master(u, NULL); + + pa_log_info("Resumed successfully..."); +} + +/* Called from main context */ +static int sink_set_state(pa_sink *sink, pa_sink_state_t state) { + struct userdata *u; + + pa_sink_assert_ref(sink); + pa_assert_se(u = sink->userdata); + + /* Please note that in contrast to the ALSA modules we call + * suspend/unsuspend from main context here! */ + + switch (state) { + case PA_SINK_SUSPENDED: + pa_assert(PA_SINK_OPENED(pa_sink_get_state(u->sink))); + + suspend(u); + break; + + case PA_SINK_IDLE: + case PA_SINK_RUNNING: + + if (pa_sink_get_state(u->sink) == PA_SINK_SUSPENDED) + unsuspend(u); + + break; + + case PA_SINK_UNLINKED: + case PA_SINK_INIT: + ; + } + + return 0; +} + +/* Called from thread context of the master */ +static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SINK(o)->userdata; + + switch (code) { + + case PA_SINK_MESSAGE_SET_STATE: + pa_atomic_store(&u->thread_info.running, PA_PTR_TO_UINT(data) == PA_SINK_RUNNING); + break; + + case PA_SINK_MESSAGE_GET_LATENCY: + + /* This code will only be called when running in NULL + * mode, i.e. when no output is attached. See + * sink_get_latency_cb() below */ + + if (u->thread_info.in_null_mode) { + struct timeval now; + + if (pa_timeval_cmp(&u->thread_info.timestamp, pa_rtclock_get(&now)) > 0) { + *((pa_usec_t*) data) = pa_timeval_diff(&u->thread_info.timestamp, &now); + break; + } + } + + *((pa_usec_t*) data) = 0; + + break; + + case SINK_MESSAGE_ADD_OUTPUT: { + struct output *op = data; + + PA_LLIST_PREPEND(struct output, u->thread_info.active_outputs, op); + + pa_assert(!op->outq_rtpoll_item); + + /* Create pa_asyncmsgq to the sink thread */ + + op->outq_rtpoll_item = pa_rtpoll_item_new_asyncmsgq( + u->rtpoll, + PA_RTPOLL_EARLY-1, /* This item is very important */ + op->outq); + + return 0; + } + + case SINK_MESSAGE_REMOVE_OUTPUT: { + struct output *op = data; - return - pa_sink_input_get_latency(u->master->sink_input) + - pa_sink_get_latency(u->master->sink_input->sink); + PA_LLIST_REMOVE(struct output, u->thread_info.active_outputs, op); + + /* Remove the q that leads from this output to the sink thread */ + + pa_assert(op->outq_rtpoll_item); + pa_rtpoll_item_free(op->outq_rtpoll_item); + op->outq_rtpoll_item = NULL; + + return 0; + } + + case SINK_MESSAGE_NEED: + render_memblock(u, data, (size_t) offset); + return 0; + } + + return pa_sink_process_msg(o, code, data, offset, chunk); } -static void sink_notify(pa_sink *s) { +/* Called from main context */ +static pa_usec_t sink_get_latency_cb(pa_sink *s) { struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (u->master) { + /* If we have a master sink, we just return the latency of it + * and add our own buffering on top */ + + if (!u->master->sink_input) + return 0; + + return + pa_sink_input_get_latency(u->master->sink_input) + + pa_sink_get_latency(u->master->sink); + + } else { + pa_usec_t usec = 0; + + /* We have no master, hence let's ask our own thread which + * implements the NULL sink */ + + if (pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0) + return 0; + + return usec; + } +} + +static void update_description(struct userdata *u) { + int first = 1; + char *t; + struct output *o; + uint32_t idx; + + pa_assert(u); + + if (pa_idxset_isempty(u->outputs)) { + pa_sink_set_description(u->sink, "Simultaneous output"); + return; + } + + t = pa_xstrdup("Simultaneous output to"); + + for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) { + char *e; + + if (first) { + e = pa_sprintf_malloc("%s %s", t, o->sink->description); + first = 0; + } else + e = pa_sprintf_malloc("%s, %s", t, o->sink->description); + + pa_xfree(t); + t = e; + } + + pa_sink_set_description(u->sink, t); + pa_xfree(t); +} + +static void update_master(struct userdata *u, struct output *o) { + pa_assert(u); + + if (u->master == o) + return; + + if ((u->master = o)) + pa_log_info("Master sink is now '%s'", o->sink_input->sink->name); + else + pa_log_info("No master selected, lacking suitable outputs."); +} + +static void pick_master(struct userdata *u, struct output *except) { struct output *o; + uint32_t idx; + pa_assert(u); + + if (u->master && + u->master != except && + u->master->sink_input && + PA_SINK_OPENED(pa_sink_get_state(u->master->sink))) { + update_master(u, u->master); + return; + } - assert(s); - u = s->userdata; - assert(u); + for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) + if (o != except && + o->sink_input && + PA_SINK_OPENED(pa_sink_get_state(o->sink))) { + update_master(u, o); + return; + } - for (o = u->outputs; o; o = o->next) - pa_sink_notify(o->sink_input->sink); + update_master(u, NULL); } -static struct output *output_new(struct userdata *u, pa_sink *sink, int resample_method) { - struct output *o = NULL; - char t[256]; +static int output_create_sink_input(struct output *o) { pa_sink_input_new_data data; + char *t; - assert(u && sink && u->sink); + pa_assert(o); - o = pa_xmalloc(sizeof(struct output)); - o->userdata = u; + if (o->sink_input) + return 0; + + t = pa_sprintf_malloc("Simultaneous output on %s", o->sink->description); - o->counter = 0; + pa_sink_input_new_data_init(&data); + data.sink = o->sink; + data.driver = __FILE__; + data.name = t; + pa_sink_input_new_data_set_sample_spec(&data, &o->userdata->sink->sample_spec); + pa_sink_input_new_data_set_channel_map(&data, &o->userdata->sink->channel_map); + data.module = o->userdata->module; + data.resample_method = o->userdata->resample_method; + + o->sink_input = pa_sink_input_new(o->userdata->core, &data, PA_SINK_INPUT_VARIABLE_RATE|PA_SINK_INPUT_DONT_MOVE); + + pa_xfree(t); + + if (!o->sink_input) + return -1; + + o->sink_input->parent.process_msg = sink_input_process_msg; + o->sink_input->peek = sink_input_peek_cb; + o->sink_input->drop = sink_input_drop_cb; + o->sink_input->attach = sink_input_attach_cb; + o->sink_input->detach = sink_input_detach_cb; + o->sink_input->kill = sink_input_kill_cb; + o->sink_input->userdata = o; + + + return 0; +} + +static struct output *output_new(struct userdata *u, pa_sink *sink) { + struct output *o; + + pa_assert(u); + pa_assert(sink); + pa_assert(u->sink); + + o = pa_xnew(struct output, 1); + o->userdata = u; + o->inq = pa_asyncmsgq_new(0); + o->outq = pa_asyncmsgq_new(0); + o->inq_rtpoll_item = NULL; + o->outq_rtpoll_item = NULL; + o->sink = sink; + o->sink_input = NULL; o->memblockq = pa_memblockq_new( 0, MEMBLOCKQ_MAXLENGTH, @@ -251,90 +776,151 @@ static struct output *output_new(struct userdata *u, pa_sink *sink, int resample 0, NULL); - snprintf(t, sizeof(t), "Output stream #%u of sink %s", u->n_outputs+1, u->sink->name); + pa_assert_se(pa_idxset_put(u->outputs, o, NULL) == 0); - pa_sink_input_new_data_init(&data); - data.sink = sink; - data.driver = __FILE__; - data.name = t; - pa_sink_input_new_data_set_sample_spec(&data, &u->sink->sample_spec); - pa_sink_input_new_data_set_channel_map(&data, &u->sink->channel_map); - data.module = u->module; + if (u->sink && PA_SINK_LINKED(pa_sink_get_state(u->sink))) + pa_asyncmsgq_send(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_ADD_OUTPUT, o, 0, NULL); + else { + /* If the sink is not yet started, we need to do the activation ourselves */ + PA_LLIST_PREPEND(struct output, u->thread_info.active_outputs, o); - if (!(o->sink_input = pa_sink_input_new(u->core, &data, PA_SINK_INPUT_VARIABLE_RATE))) - goto fail; + o->outq_rtpoll_item = pa_rtpoll_item_new_asyncmsgq( + u->rtpoll, + PA_RTPOLL_EARLY-1, /* This item is very important */ + o->outq); + } - o->sink_input->get_latency = sink_input_get_latency_cb; - o->sink_input->peek = sink_input_peek_cb; - o->sink_input->drop = sink_input_drop_cb; - o->sink_input->kill = sink_input_kill_cb; - o->sink_input->userdata = o; + if (PA_SINK_OPENED(pa_sink_get_state(u->sink)) || pa_sink_get_state(u->sink) == PA_SINK_INIT) { + pa_sink_suspend(sink, FALSE); + + if (PA_SINK_OPENED(pa_sink_get_state(sink))) + if (output_create_sink_input(o) < 0) + goto fail; + } + + + update_description(u); - PA_LLIST_PREPEND(struct output, u->outputs, o); - u->n_outputs++; return o; fail: if (o) { + pa_idxset_remove_by_data(u->outputs, o, NULL); + if (o->sink_input) { - pa_sink_input_disconnect(o->sink_input); + pa_sink_input_unlink(o->sink_input); pa_sink_input_unref(o->sink_input); } if (o->memblockq) pa_memblockq_free(o->memblockq); + if (o->inq) + pa_asyncmsgq_unref(o->inq); + + if (o->outq) + pa_asyncmsgq_unref(o->outq); + pa_xfree(o); } return NULL; } -static void output_free(struct output *o) { - assert(o); - PA_LLIST_REMOVE(struct output, o->userdata->outputs, o); - o->userdata->n_outputs--; - pa_memblockq_free(o->memblockq); - pa_sink_input_disconnect(o->sink_input); - pa_sink_input_unref(o->sink_input); - pa_xfree(o); +static pa_hook_result_t sink_new_hook_cb(pa_core *c, pa_sink *s, struct userdata* u) { + struct output *o; + + pa_core_assert_ref(c); + pa_sink_assert_ref(s); + pa_assert(u); + pa_assert(u->automatic); + + if (!(s->flags & PA_SINK_HARDWARE) || s == u->sink) + return PA_HOOK_OK; + + pa_log_info("Configuring new sink: %s", s->name); + + if (!(o = output_new(u, s))) { + pa_log("Failed to create sink input on sink '%s'.", s->name); + return PA_HOOK_OK; + } + + if (o->sink_input) + pa_sink_input_put(o->sink_input); + + pick_master(u, NULL); + + return PA_HOOK_OK; } -static void clear_up(struct userdata *u) { +static pa_hook_result_t sink_unlink_hook_cb(pa_core *c, pa_sink *s, struct userdata* u) { struct output *o; - assert(u); + uint32_t idx; - if (u->time_event) { - u->core->mainloop->time_free(u->time_event); - u->time_event = NULL; - } + pa_assert(c); + pa_sink_assert_ref(s); + pa_assert(u); - while ((o = u->outputs)) - output_free(o); + if (s == u->sink) + return PA_HOOK_OK; - u->master = NULL; + for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) + if (o->sink == s) + break; - if (u->sink) { - pa_sink_disconnect(u->sink); - pa_sink_unref(u->sink); - u->sink = NULL; + if (!o) + return PA_HOOK_OK; + + pa_log_info("Unconfiguring sink: %s", s->name); + + output_free(o); + + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_state_changed_hook_cb(pa_core *c, pa_sink *s, struct userdata* u) { + struct output *o; + uint32_t idx; + pa_sink_state_t state; + + if (s == u->sink) + return PA_HOOK_OK; + + for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) + if (o->sink == s) + break; + + if (!o) + return PA_HOOK_OK; + + state = pa_sink_get_state(s); + + if (PA_SINK_OPENED(state) && PA_SINK_OPENED(pa_sink_get_state(u->sink)) && !o->sink_input) { + enable_output(o); + pick_master(u, NULL); + } + + if (state == PA_SINK_SUSPENDED && o->sink_input) { + disable_output(o); + pick_master(u, o); } + + return PA_HOOK_OK; } -int pa__init(pa_core *c, pa_module*m) { +int pa__init(pa_module*m) { struct userdata *u; pa_modargs *ma = NULL; const char *master_name, *slaves, *rm; - pa_sink *master_sink; - char *n = NULL; - const char*split_state; - struct timeval tv; - int resample_method = -1; + pa_sink *master_sink = NULL; + int resample_method = PA_RESAMPLER_TRIVIAL; pa_sample_spec ss; pa_channel_map map; + struct output *o; + uint32_t idx; - assert(c && m); + pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { pa_log("failed to parse module arguments"); @@ -349,116 +935,256 @@ int pa__init(pa_core *c, pa_module*m) { } u = pa_xnew(struct userdata, 1); + u->core = m->core; + u->module = m; m->userdata = u; u->sink = NULL; - u->n_outputs = 0; u->master = NULL; - u->module = m; - u->core = c; u->time_event = NULL; u->adjust_time = DEFAULT_ADJUST_TIME; - PA_LLIST_HEAD_INIT(struct output, u->outputs); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop); + u->rtpoll = pa_rtpoll_new(); + u->thread = NULL; + u->resample_method = resample_method; + u->outputs = pa_idxset_new(NULL, NULL); + memset(&u->adjust_timestamp, 0, sizeof(u->adjust_timestamp)); + u->sink_new_slot = u->sink_unlink_slot = u->sink_state_changed_slot = NULL; + PA_LLIST_HEAD_INIT(struct output, u->thread_info.active_outputs); + pa_atomic_store(&u->thread_info.running, FALSE); + u->thread_info.in_null_mode = FALSE; + pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq); if (pa_modargs_get_value_u32(ma, "adjust_time", &u->adjust_time) < 0) { - pa_log("failed to parse adjust_time value"); + pa_log("Failed to parse adjust_time value"); goto fail; } - if (!(master_name = pa_modargs_get_value(ma, "master", NULL)) || !(slaves = pa_modargs_get_value(ma, "slaves", NULL))) { - pa_log("no master or slave sinks specified"); + master_name = pa_modargs_get_value(ma, "master", NULL); + slaves = pa_modargs_get_value(ma, "slaves", NULL); + if (!master_name != !slaves) { + pa_log("No master or slave sinks specified"); goto fail; } - if (!(master_sink = pa_namereg_get(c, master_name, PA_NAMEREG_SINK, 1))) { - pa_log("invalid master sink '%s'", master_name); - goto fail; + if (master_name) { + if (!(master_sink = pa_namereg_get(m->core, master_name, PA_NAMEREG_SINK, 1))) { + pa_log("Invalid master sink '%s'", master_name); + goto fail; + } + + ss = master_sink->sample_spec; + u->automatic = FALSE; + } else { + master_sink = NULL; + ss = m->core->default_sample_spec; + u->automatic = TRUE; } - ss = master_sink->sample_spec; if ((pa_modargs_get_sample_spec(ma, &ss) < 0)) { - pa_log("invalid sample specification."); + pa_log("Invalid sample specification."); goto fail; } - if (ss.channels == master_sink->sample_spec.channels) + if (master_sink && ss.channels == master_sink->sample_spec.channels) map = master_sink->channel_map; else pa_channel_map_init_auto(&map, ss.channels, PA_CHANNEL_MAP_DEFAULT); - if ((pa_modargs_get_channel_map(ma, &map) < 0)) { - pa_log("invalid channel map."); + if ((pa_modargs_get_channel_map(ma, NULL, &map) < 0)) { + pa_log("Invalid channel map."); goto fail; } if (ss.channels != map.channels) { - pa_log("channel map and sample specification don't match."); + pa_log("Channel map and sample specification don't match."); goto fail; } - if (!(u->sink = pa_sink_new(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, &map))) { - pa_log("failed to create sink"); + if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, &map))) { + pa_log("Failed to create sink"); goto fail; } - pa_sink_set_owner(u->sink, m); - pa_sink_set_description(u->sink, "Combined Sink"); + u->sink->parent.process_msg = sink_process_msg; u->sink->get_latency = sink_get_latency_cb; - u->sink->notify = sink_notify; + u->sink->set_state = sink_set_state; u->sink->userdata = u; - if (!(u->master = output_new(u, master_sink, resample_method))) { - pa_log("failed to create master sink input on sink '%s'.", u->sink->name); - goto fail; - } + u->sink->flags = PA_SINK_LATENCY; + pa_sink_set_module(u->sink, m); + pa_sink_set_description(u->sink, "Simultaneous output"); + pa_sink_set_rtpoll(u->sink, u->rtpoll); + pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); + + u->block_size = pa_bytes_per_second(&ss) / 20; /* 50 ms */ + if (u->block_size <= 0) + u->block_size = pa_frame_size(&ss); - split_state = NULL; - while ((n = pa_split(slaves, ",", &split_state))) { - pa_sink *slave_sink; + if (!u->automatic) { + const char*split_state; + char *n = NULL; + pa_assert(slaves); - if (!(slave_sink = pa_namereg_get(c, n, PA_NAMEREG_SINK, 1))) { - pa_log("invalid slave sink '%s'", n); + /* The master and slaves have been specified manually */ + + if (!(u->master = output_new(u, master_sink))) { + pa_log("Failed to create master sink input on sink '%s'.", master_sink->name); goto fail; } - pa_xfree(n); + split_state = NULL; + while ((n = pa_split(slaves, ",", &split_state))) { + pa_sink *slave_sink; - if (!output_new(u, slave_sink, resample_method)) { - pa_log("failed to create slave sink input on sink '%s'.", slave_sink->name); - goto fail; + if (!(slave_sink = pa_namereg_get(m->core, n, PA_NAMEREG_SINK, 1)) || slave_sink == u->sink) { + pa_log("Invalid slave sink '%s'", n); + pa_xfree(n); + goto fail; + } + + pa_xfree(n); + + if (!output_new(u, slave_sink)) { + pa_log("Failed to create slave sink input on sink '%s'.", slave_sink->name); + goto fail; + } } + + if (pa_idxset_size(u->outputs) <= 1) + pa_log_warn("No slave sinks specified."); + + u->sink_new_slot = NULL; + + } else { + pa_sink *s; + + /* We're in automatic mode, we elect one hw sink to the master + * and attach all other hw sinks as slaves to it */ + + for (s = pa_idxset_first(m->core->sinks, &idx); s; s = pa_idxset_next(m->core->sinks, &idx)) { + + if (!(s->flags & PA_SINK_HARDWARE) || s == u->sink) + continue; + + if (!output_new(u, s)) { + pa_log("Failed to create sink input on sink '%s'.", s->name); + goto fail; + } + } + + u->sink_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_NEW_POST], (pa_hook_cb_t) sink_new_hook_cb, u); } - if (u->n_outputs <= 1) - pa_log_warn("WARNING: no slave sinks specified."); + u->sink_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], (pa_hook_cb_t) sink_unlink_hook_cb, u); + u->sink_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_STATE_CHANGED], (pa_hook_cb_t) sink_state_changed_hook_cb, u); + + pick_master(u, NULL); + + if (!(u->thread = pa_thread_new(thread_func, u))) { + pa_log("Failed to create thread."); + goto fail; + } + + /* Activate the sink and the sink inputs */ + pa_sink_put(u->sink); + + for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) + if (o->sink_input) + pa_sink_input_put(o->sink_input); if (u->adjust_time > 0) { + struct timeval tv; pa_gettimeofday(&tv); tv.tv_sec += u->adjust_time; - u->time_event = c->mainloop->time_new(c->mainloop, &tv, time_callback, u); + u->time_event = m->core->mainloop->time_new(m->core->mainloop, &tv, time_callback, u); } pa_modargs_free(ma); + return 0; fail: - pa_xfree(n); if (ma) pa_modargs_free(ma); - pa__done(c, m); + pa__done(m); + return -1; } -void pa__done(pa_core *c, pa_module*m) { +static void output_free(struct output *o) { + pa_assert(o); + + pick_master(o->userdata, o); + + disable_output(o); + + pa_assert_se(pa_idxset_remove_by_data(o->userdata->outputs, o, NULL)); + + update_description(o->userdata); + + if (o->inq_rtpoll_item) + pa_rtpoll_item_free(o->inq_rtpoll_item); + + if (o->outq_rtpoll_item) + pa_rtpoll_item_free(o->outq_rtpoll_item); + + if (o->inq) + pa_asyncmsgq_unref(o->inq); + + if (o->outq) + pa_asyncmsgq_unref(o->outq); + + if (o->memblockq) + pa_memblockq_free(o->memblockq); + + pa_xfree(o); +} + +void pa__done(pa_module*m) { struct userdata *u; - assert(c && m); + struct output *o; + + pa_assert(m); if (!(u = m->userdata)) return; - clear_up(u); - pa_xfree(u); -} + if (u->sink_new_slot) + pa_hook_slot_free(u->sink_new_slot); + + if (u->sink_unlink_slot) + pa_hook_slot_free(u->sink_unlink_slot); + + if (u->sink_state_changed_slot) + pa_hook_slot_free(u->sink_state_changed_slot); + + if (u->outputs) { + while ((o = pa_idxset_first(u->outputs, NULL))) + output_free(o); + + pa_idxset_free(u->outputs, NULL, NULL); + } + if (u->sink) + pa_sink_unlink(u->sink); + if (u->thread) { + pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); + pa_thread_free(u->thread); + } + + pa_thread_mq_done(&u->thread_mq); + + if (u->sink) + pa_sink_unref(u->sink); + + if (u->rtpoll) + pa_rtpoll_free(u->rtpoll); + + if (u->time_event) + u->core->mainloop->time_free(u->time_event); + + pa_xfree(u); +} diff --git a/src/modules/module-default-device-restore.c b/src/modules/module-default-device-restore.c new file mode 100644 index 00000000..a816eae8 --- /dev/null +++ b/src/modules/module-default-device-restore.c @@ -0,0 +1,103 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulsecore/core-util.h> +#include <pulsecore/module.h> +#include <pulsecore/log.h> +#include <pulsecore/namereg.h> + +#include "module-default-device-restore-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering") +PA_MODULE_DESCRIPTION("Automatically restore the default sink and source") +PA_MODULE_VERSION(PACKAGE_VERSION) + +#define DEFAULT_SINK_FILE "default-sink" +#define DEFAULT_SOURCE_FILE "default-source" + +int pa__init(pa_module *m) { + FILE *f; + + /* We never overwrite manually configured settings */ + + if (m->core->default_sink_name) + pa_log_info("Manually configured default sink, not overwriting."); + else if ((f = pa_open_config_file(NULL, DEFAULT_SINK_FILE, NULL, NULL, "r"))) { + char ln[256] = ""; + + fgets(ln, sizeof(ln)-1, f); + pa_strip_nl(ln); + fclose(f); + + if (!ln[0]) + pa_log_debug("No previous default sink setting, ignoring."); + else if (pa_namereg_get(m->core, ln, PA_NAMEREG_SINK, 1)) { + pa_namereg_set_default(m->core, ln, PA_NAMEREG_SINK); + pa_log_debug("Restored default sink '%s'.", ln); + } else + pa_log_info("Saved default sink '%s' not existant, not restoring default sink setting.", ln); + } + + if (m->core->default_source_name) + pa_log_info("Manually configured default source, not overwriting."); + else if ((f = pa_open_config_file(NULL, DEFAULT_SOURCE_FILE, NULL, NULL, "r"))) { + char ln[256] = ""; + + fgets(ln, sizeof(ln)-1, f); + pa_strip_nl(ln); + fclose(f); + + if (!ln[0]) + pa_log_debug("No previous default source setting, ignoring."); + else if (pa_namereg_get(m->core, ln, PA_NAMEREG_SOURCE, 1)) { + pa_namereg_set_default(m->core, ln, PA_NAMEREG_SOURCE); + pa_log_debug("Restored default source '%s'.", ln); + } else + pa_log_info("Saved default source '%s' not existant, not restoring default source setting.", ln); + } + + return 0; +} + +void pa__done(pa_module*m) { + FILE *f; + + if ((f = pa_open_config_file(NULL, DEFAULT_SINK_FILE, NULL, NULL, "w"))) { + const char *n = pa_namereg_get_default_sink_name(m->core); + fprintf(f, "%s\n", n ? n : ""); + fclose(f); + } + + if ((f = pa_open_config_file(NULL, DEFAULT_SOURCE_FILE, NULL, NULL, "w"))) { + const char *n = pa_namereg_get_default_source_name(m->core); + fprintf(f, "%s\n", n ? n : ""); + fclose(f); + } +} + + + diff --git a/src/modules/module-defs.h.m4 b/src/modules/module-defs.h.m4 index c961412d..5bff748e 100644 --- a/src/modules/module-defs.h.m4 +++ b/src/modules/module-defs.h.m4 @@ -18,8 +18,8 @@ gen_symbol(pa__get_description) gen_symbol(pa__get_usage) gen_symbol(pa__get_version) -int pa__init(struct pa_core *c, struct pa_module*m); -void pa__done(struct pa_core *c, struct pa_module*m); +int pa__init(pa_module*m); +void pa__done(pa_module*m); const char* pa__get_author(void); const char* pa__get_description(void); diff --git a/src/modules/module-detect.c b/src/modules/module-detect.c index 41b68ac3..7dc25243 100644 --- a/src/modules/module-detect.c +++ b/src/modules/module-detect.c @@ -28,7 +28,6 @@ #endif #include <stdio.h> -#include <assert.h> #include <unistd.h> #include <string.h> #include <stdlib.h> @@ -44,6 +43,7 @@ #include <pulsecore/modargs.h> #include <pulsecore/log.h> #include <pulsecore/core-util.h> +#include <pulsecore/macro.h> #include "module-detect-symdef.h" @@ -52,6 +52,11 @@ PA_MODULE_DESCRIPTION("Detect available audio hardware and load matching drivers PA_MODULE_VERSION(PACKAGE_VERSION) PA_MODULE_USAGE("just-one=<boolean>") +static const char* const valid_modargs[] = { + "just-one", + NULL +}; + #ifdef HAVE_ALSA static int detect_alsa(pa_core *c, int just_one) { @@ -96,7 +101,7 @@ static int detect_alsa(pa_core *c, int just_one) { if (subdevice != 0) continue; - snprintf(args, sizeof(args), "device=hw:%u", device); + pa_snprintf(args, sizeof(args), "device=hw:%u", device); if (!pa_module_load(c, is_sink ? "module-alsa-sink" : "module-alsa-source", args)) continue; @@ -139,7 +144,7 @@ static int detect_oss(pa_core *c, int just_one) { line[strcspn(line, "\r\n")] = 0; if (!b) { - b = strcmp(line, "Audio devices:") == 0 || strcmp(line, "Installed devices:") == 0; + b = strcmp(line, "Audio devices:") == 0 || strcmp(line, "Installed devices:") == 0; continue; } @@ -148,20 +153,20 @@ static int detect_oss(pa_core *c, int just_one) { if (sscanf(line, "%u: ", &device) == 1) { if (device == 0) - snprintf(args, sizeof(args), "device=/dev/dsp"); + pa_snprintf(args, sizeof(args), "device=/dev/dsp"); else - snprintf(args, sizeof(args), "device=/dev/dsp%u", device); + pa_snprintf(args, sizeof(args), "device=/dev/dsp%u", device); if (!pa_module_load(c, "module-oss", args)) continue; - } else if (sscanf(line, "pcm%u: ", &device) == 1) { + } else if (sscanf(line, "pcm%u: ", &device) == 1) { /* FreeBSD support, the devices are named /dev/dsp0.0, dsp0.1 and so on */ - snprintf(args, sizeof(args), "device=/dev/dsp%u.0", device); + pa_snprintf(args, sizeof(args), "device=/dev/dsp%u.0", device); if (!pa_module_load(c, "module-oss", args)) continue; - } + } n++; @@ -193,7 +198,7 @@ static int detect_solaris(pa_core *c, int just_one) { if (!S_ISCHR(s.st_mode)) return 0; - snprintf(args, sizeof(args), "device=%s", dev); + pa_snprintf(args, sizeof(args), "device=%s", dev); if (!pa_module_load(c, "module-solaris", args)) return 0; @@ -215,17 +220,11 @@ static int detect_waveout(pa_core *c, int just_one) { } #endif -int pa__init(pa_core *c, pa_module*m) { +int pa__init(pa_module*m) { int just_one = 0, n = 0; pa_modargs *ma; - static const char* const valid_modargs[] = { - "just-one", - NULL - }; - - assert(c); - assert(m); + pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { pa_log("Failed to parse module arguments"); @@ -238,16 +237,16 @@ int pa__init(pa_core *c, pa_module*m) { } #if HAVE_ALSA - if ((n = detect_alsa(c, just_one)) <= 0) + if ((n = detect_alsa(m->core, just_one)) <= 0) #endif #if HAVE_OSS - if ((n = detect_oss(c, just_one)) <= 0) + if ((n = detect_oss(m->core, just_one)) <= 0) #endif #if HAVE_SOLARIS - if ((n = detect_solaris(c, just_one)) <= 0) + if ((n = detect_solaris(m->core, just_one)) <= 0) #endif #if OS_IS_WIN32 - if ((n = detect_waveout(c, just_one)) <= 0) + if ((n = detect_waveout(m->core, just_one)) <= 0) #endif { pa_log_warn("failed to detect any sound hardware."); @@ -269,9 +268,3 @@ fail: return -1; } - - -void pa__done(PA_GCC_UNUSED pa_core *c, PA_GCC_UNUSED pa_module*m) { - /* NOP */ -} - diff --git a/src/modules/module-esound-compat-spawnfd.c b/src/modules/module-esound-compat-spawnfd.c index 1aecade5..f0f73fcf 100644 --- a/src/modules/module-esound-compat-spawnfd.c +++ b/src/modules/module-esound-compat-spawnfd.c @@ -26,7 +26,6 @@ #endif #include <unistd.h> -#include <assert.h> #include <string.h> #include <errno.h> @@ -48,23 +47,25 @@ static const char* const valid_modargs[] = { NULL, }; -int pa__init(pa_core *c, pa_module*m) { +int pa__init(pa_module*m) { pa_modargs *ma = NULL; int ret = -1, fd = -1; char x = 1; - assert(c && m); + + pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs)) || pa_modargs_get_value_s32(ma, "fd", &fd) < 0 || fd < 0) { + pa_log("Failed to parse module arguments"); goto finish; } if (pa_loop_write(fd, &x, sizeof(x), NULL) != sizeof(x)) - pa_log("WARNING: write(%u, 1, 1) failed: %s", fd, pa_cstrerror(errno)); + pa_log_warn("write(%u, 1, 1) failed: %s", fd, pa_cstrerror(errno)); - close(fd); + pa_assert_se(pa_close(fd) == 0); pa_module_unload_request(m); @@ -76,9 +77,3 @@ finish: return ret; } - -void pa__done(pa_core *c, pa_module*m) { - assert(c && m); -} - - diff --git a/src/modules/module-esound-compat-spawnpid.c b/src/modules/module-esound-compat-spawnpid.c index a9fd166d..6562fe28 100644 --- a/src/modules/module-esound-compat-spawnpid.c +++ b/src/modules/module-esound-compat-spawnpid.c @@ -25,7 +25,6 @@ #endif #include <unistd.h> -#include <assert.h> #include <string.h> #include <errno.h> #include <signal.h> @@ -48,11 +47,12 @@ static const char* const valid_modargs[] = { NULL, }; -int pa__init(pa_core *c, pa_module*m) { +int pa__init(pa_module*m) { pa_modargs *ma = NULL; int ret = -1; uint32_t pid = 0; - assert(c && m); + + pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs)) || pa_modargs_get_value_u32(ma, "pid", &pid) < 0 || @@ -62,7 +62,7 @@ int pa__init(pa_core *c, pa_module*m) { } if (kill(pid, SIGUSR1) < 0) - pa_log("WARNING: kill(%u) failed: %s", pid, pa_cstrerror(errno)); + pa_log_warn("kill(%u) failed: %s", pid, pa_cstrerror(errno)); pa_module_unload_request(m); @@ -74,9 +74,3 @@ finish: return ret; } - -void pa__done(pa_core *c, pa_module*m) { - assert(c && m); -} - - diff --git a/src/modules/module-esound-sink.c b/src/modules/module-esound-sink.c index 26638d9d..8b46637e 100644 --- a/src/modules/module-esound-sink.c +++ b/src/modules/module-esound-sink.c @@ -28,14 +28,23 @@ #include <stdlib.h> #include <sys/stat.h> #include <stdio.h> -#include <assert.h> #include <errno.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <limits.h> +#include <poll.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <sys/ioctl.h> + +#ifdef HAVE_LINUX_SOCKIOS_H +#include <linux/sockios.h> +#endif #include <pulse/xmalloc.h> +#include <pulse/timeval.h> #include <pulsecore/core-error.h> #include <pulsecore/iochannel.h> @@ -47,27 +56,37 @@ #include <pulsecore/socket-client.h> #include <pulsecore/esound.h> #include <pulsecore/authkey.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/thread.h> +#include <pulsecore/time-smoother.h> +#include <pulsecore/rtclock.h> +#include <pulsecore/socket-util.h> #include "module-esound-sink-symdef.h" PA_MODULE_AUTHOR("Lennart Poettering") PA_MODULE_DESCRIPTION("ESOUND Sink") PA_MODULE_VERSION(PACKAGE_VERSION) -PA_MODULE_USAGE("sink_name=<name for the sink> server=<address> cookie=<filename> format=<sample format> channels=<number of channels> rate=<sample rate>") +PA_MODULE_USAGE( + "sink_name=<name for the sink> " + "server=<address> cookie=<filename> " + "format=<sample format> " + "channels=<number of channels> " + "rate=<sample rate>") -#define DEFAULT_SINK_NAME "esound_output" +#define DEFAULT_SINK_NAME "esound_out" struct userdata { pa_core *core; - + pa_module *module; pa_sink *sink; - pa_iochannel *io; - pa_socket_client *client; - pa_defer_event *defer_event; + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; + pa_rtpoll_item *rtpoll_item; + pa_thread *thread; pa_memchunk memchunk; - pa_module *module; void *write_data; size_t write_length, write_index; @@ -75,12 +94,28 @@ struct userdata { void *read_data; size_t read_length, read_index; - enum { STATE_AUTH, STATE_LATENCY, STATE_RUNNING, STATE_DEAD } state; + enum { + STATE_AUTH, + STATE_LATENCY, + STATE_PREPARE, + STATE_RUNNING, + STATE_DEAD + } state; pa_usec_t latency; esd_format_t format; int32_t rate; + + pa_smoother *smoother; + int fd; + + int64_t offset; + + pa_iochannel *io; + pa_socket_client *client; + + size_t block_size; }; static const char* const valid_modargs[] = { @@ -93,42 +128,211 @@ static const char* const valid_modargs[] = { NULL }; -static void cancel(struct userdata *u) { - assert(u); +enum { + SINK_MESSAGE_PASS_SOCKET = PA_SINK_MESSAGE_MAX +}; - u->state = STATE_DEAD; +static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SINK(o)->userdata; - if (u->io) { - pa_iochannel_free(u->io); - u->io = NULL; - } + switch (code) { - if (u->defer_event) { - u->core->mainloop->defer_free(u->defer_event); - u->defer_event = NULL; - } + case PA_SINK_MESSAGE_SET_STATE: - if (u->sink) { - pa_sink_disconnect(u->sink); - pa_sink_unref(u->sink); - u->sink = NULL; + switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) { + + case PA_SINK_SUSPENDED: + pa_assert(PA_SINK_OPENED(u->sink->thread_info.state)); + + pa_smoother_pause(u->smoother, pa_rtclock_usec()); + break; + + case PA_SINK_IDLE: + case PA_SINK_RUNNING: + + if (u->sink->thread_info.state == PA_SINK_SUSPENDED) + pa_smoother_resume(u->smoother, pa_rtclock_usec()); + + break; + + case PA_SINK_UNLINKED: + case PA_SINK_INIT: + ; + } + + break; + + case PA_SINK_MESSAGE_GET_LATENCY: { + pa_usec_t w, r; + + r = pa_smoother_get(u->smoother, pa_rtclock_usec()); + w = pa_bytes_to_usec(u->offset + u->memchunk.length, &u->sink->sample_spec); + + *((pa_usec_t*) data) = w > r ? w - r : 0; + break; + } + + case SINK_MESSAGE_PASS_SOCKET: { + struct pollfd *pollfd; + + pa_assert(!u->rtpoll_item); + + u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1); + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + pollfd->fd = u->fd; + pollfd->events = pollfd->revents = 0; + + return 0; + } } - if (u->module) { - pa_module_unload_request(u->module); - u->module = NULL; + return pa_sink_process_msg(o, code, data, offset, chunk); +} + +static void thread_func(void *userdata) { + struct userdata *u = userdata; + int write_type = 0; + + pa_assert(u); + + pa_log_debug("Thread starting up"); + + pa_thread_mq_install(&u->thread_mq); + pa_rtpoll_install(u->rtpoll); + + pa_smoother_set_time_offset(u->smoother, pa_rtclock_usec()); + + for (;;) { + int ret; + + if (u->rtpoll_item) { + struct pollfd *pollfd; + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + + /* Render some data and write it to the fifo */ + if (PA_SINK_OPENED(u->sink->thread_info.state) && pollfd->revents) { + pa_usec_t usec; + int64_t n; + + for (;;) { + ssize_t l; + void *p; + + if (u->memchunk.length <= 0) + pa_sink_render(u->sink, u->block_size, &u->memchunk); + + pa_assert(u->memchunk.length > 0); + + p = pa_memblock_acquire(u->memchunk.memblock); + l = pa_write(u->fd, (uint8_t*) p + u->memchunk.index, u->memchunk.length, &write_type); + pa_memblock_release(u->memchunk.memblock); + + pa_assert(l != 0); + + if (l < 0) { + + if (errno == EINTR) + continue; + else if (errno == EAGAIN) { + + /* OK, we filled all socket buffers up + * now. */ + goto filled_up; + + } else { + pa_log("Failed to write data to FIFO: %s", pa_cstrerror(errno)); + goto fail; + } + + } else { + u->offset += l; + + u->memchunk.index += l; + u->memchunk.length -= l; + + if (u->memchunk.length <= 0) { + pa_memblock_unref(u->memchunk.memblock); + pa_memchunk_reset(&u->memchunk); + } + + pollfd->revents = 0; + + if (u->memchunk.length > 0) + + /* OK, we wrote less that we asked for, + * hence we can assume that the socket + * buffers are full now */ + goto filled_up; + } + } + + filled_up: + + /* At this spot we know that the socket buffers are + * fully filled up. This is the best time to estimate + * the playback position of the server */ + + n = u->offset; + +#ifdef SIOCOUTQ + { + int l; + if (ioctl(u->fd, SIOCOUTQ, &l) >= 0 && l > 0) + n -= l; + } +#endif + + usec = pa_bytes_to_usec(n, &u->sink->sample_spec); + + if (usec > u->latency) + usec -= u->latency; + else + usec = 0; + + pa_smoother_put(u->smoother, pa_rtclock_usec(), usec); + } + + /* Hmm, nothing to do. Let's sleep */ + pollfd->events = PA_SINK_OPENED(u->sink->thread_info.state) ? POLLOUT : 0; + } + + if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) + goto fail; + + if (ret == 0) + goto finish; + + if (u->rtpoll_item) { + struct pollfd* pollfd; + + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + + if (pollfd->revents & ~POLLOUT) { + pa_log("FIFO shutdown."); + goto fail; + } + } } + +fail: + /* If this was no regular exit from the loop we have to continue + * processing messages until we received PA_MESSAGE_SHUTDOWN */ + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + +finish: + pa_log_debug("Thread shutting down"); } static int do_write(struct userdata *u) { ssize_t r; - assert(u); + pa_assert(u); if (!pa_iochannel_is_writable(u->io)) return 0; if (u->write_data) { - assert(u->write_index < u->write_length); + pa_assert(u->write_index < u->write_length); if ((r = pa_iochannel_write(u->io, (uint8_t*) u->write_data + u->write_index, u->write_length - u->write_index)) <= 0) { pa_log("write() failed: %s", pa_cstrerror(errno)); @@ -136,45 +340,44 @@ static int do_write(struct userdata *u) { } u->write_index += r; - assert(u->write_index <= u->write_length); + pa_assert(u->write_index <= u->write_length); if (u->write_index == u->write_length) { - free(u->write_data); + pa_xfree(u->write_data); u->write_data = NULL; u->write_index = u->write_length = 0; } - } else if (u->state == STATE_RUNNING) { - pa_module_set_used(u->module, pa_sink_used_by(u->sink)); + } - if (!u->memchunk.length) - if (pa_sink_render(u->sink, 8192, &u->memchunk) < 0) - return 0; + if (!u->write_data && u->state == STATE_PREPARE) { + /* OK, we're done with sending all control data we need to, so + * let's hand the socket over to the IO thread now */ - assert(u->memchunk.memblock && u->memchunk.length); + pa_assert(u->fd < 0); + u->fd = pa_iochannel_get_send_fd(u->io); - if ((r = pa_iochannel_write(u->io, (uint8_t*) u->memchunk.memblock->data + u->memchunk.index, u->memchunk.length)) < 0) { - pa_log("write() failed: %s", pa_cstrerror(errno)); - return -1; - } + pa_iochannel_set_noclose(u->io, TRUE); + pa_iochannel_free(u->io); + u->io = NULL; - u->memchunk.index += r; - u->memchunk.length -= r; + pa_make_tcp_socket_low_delay(u->fd); - if (u->memchunk.length <= 0) { - pa_memblock_unref(u->memchunk.memblock); - u->memchunk.memblock = NULL; - } + pa_log_info("Connection authenticated, handing fd to IO thread..."); + + pa_asyncmsgq_post(u->thread_mq.inq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_PASS_SOCKET, NULL, 0, NULL, NULL); + u->state = STATE_RUNNING; } return 0; } static int handle_response(struct userdata *u) { - assert(u); + pa_assert(u); switch (u->state) { + case STATE_AUTH: - assert(u->read_length == sizeof(int32_t)); + pa_assert(u->read_length == sizeof(int32_t)); /* Process auth data */ if (!*(int32_t*) u->read_data) { @@ -183,14 +386,14 @@ static int handle_response(struct userdata *u) { } /* Request latency data */ - assert(!u->write_data); + pa_assert(!u->write_data); *(int32_t*) (u->write_data = pa_xmalloc(u->write_length = sizeof(int32_t))) = ESD_PROTO_LATENCY; u->write_index = 0; u->state = STATE_LATENCY; /* Space for next response */ - assert(u->read_length >= sizeof(int32_t)); + pa_assert(u->read_length >= sizeof(int32_t)); u->read_index = 0; u->read_length = sizeof(int32_t); @@ -198,17 +401,17 @@ static int handle_response(struct userdata *u) { case STATE_LATENCY: { int32_t *p; - assert(u->read_length == sizeof(int32_t)); + pa_assert(u->read_length == sizeof(int32_t)); /* Process latency info */ u->latency = (pa_usec_t) ((double) (*(int32_t*) u->read_data) * 1000000 / 44100); if (u->latency > 10000000) { - pa_log("WARNING! Invalid latency information received from server"); + pa_log_warn("Invalid latency information received from server"); u->latency = 0; } /* Create stream */ - assert(!u->write_data); + pa_assert(!u->write_data); p = u->write_data = pa_xmalloc0(u->write_length = sizeof(int32_t)*3+ESD_NAME_MAX); *(p++) = ESD_PROTO_STREAM_PLAY; *(p++) = u->format; @@ -216,7 +419,7 @@ static int handle_response(struct userdata *u) { pa_strlcpy((char*) p, "PulseAudio Tunnel", ESD_NAME_MAX); u->write_index = 0; - u->state = STATE_RUNNING; + u->state = STATE_PREPARE; /* Don't read any further */ pa_xfree(u->read_data); @@ -227,14 +430,14 @@ static int handle_response(struct userdata *u) { } default: - abort(); + pa_assert_not_reached(); } return 0; } static int do_read(struct userdata *u) { - assert(u); + pa_assert(u); if (!pa_iochannel_is_readable(u->io)) return 0; @@ -245,16 +448,15 @@ static int do_read(struct userdata *u) { if (!u->read_data) return 0; - assert(u->read_index < u->read_length); + pa_assert(u->read_index < u->read_length); if ((r = pa_iochannel_read(u->io, (uint8_t*) u->read_data + u->read_index, u->read_length - u->read_index)) <= 0) { pa_log("read() failed: %s", r < 0 ? pa_cstrerror(errno) : "EOF"); - cancel(u); return -1; } u->read_index += r; - assert(u->read_index <= u->read_length); + pa_assert(u->read_index <= u->read_length); if (u->read_index == u->read_length) return handle_response(u); @@ -263,42 +465,19 @@ static int do_read(struct userdata *u) { return 0; } -static void do_work(struct userdata *u) { - assert(u); - - u->core->mainloop->defer_enable(u->defer_event, 0); - - if (do_read(u) < 0 || do_write(u) < 0) - cancel(u); -} - -static void notify_cb(pa_sink*s) { - struct userdata *u = s->userdata; - assert(s && u); - - if (pa_iochannel_is_writable(u->io)) - u->core->mainloop->defer_enable(u->defer_event, 1); -} - -static pa_usec_t get_latency_cb(pa_sink *s) { - struct userdata *u = s->userdata; - assert(s && u); +static void io_callback(PA_GCC_UNUSED pa_iochannel *io, void*userdata) { + struct userdata *u = userdata; + pa_assert(u); - return - u->latency + - (u->memchunk.memblock ? pa_bytes_to_usec(u->memchunk.length, &s->sample_spec) : 0); -} + if (do_read(u) < 0 || do_write(u) < 0) { -static void defer_callback(PA_GCC_UNUSED pa_mainloop_api *m, PA_GCC_UNUSED pa_defer_event*e, void *userdata) { - struct userdata *u = userdata; - assert(u); - do_work(u); -} + if (u->io) { + pa_iochannel_free(u->io); + u->io = NULL; + } -static void io_callback(PA_GCC_UNUSED pa_iochannel *io, void*userdata) { - struct userdata *u = userdata; - assert(u); - do_work(u); + pa_module_unload_request(u->module); + } } static void on_connection(PA_GCC_UNUSED pa_socket_client *c, pa_iochannel*io, void *userdata) { @@ -308,30 +487,34 @@ static void on_connection(PA_GCC_UNUSED pa_socket_client *c, pa_iochannel*io, vo u->client = NULL; if (!io) { - pa_log("connection failed: %s", pa_cstrerror(errno)); - cancel(u); + pa_log("Connection failed: %s", pa_cstrerror(errno)); + pa_module_unload_request(u->module); return; } + pa_assert(!u->io); u->io = io; pa_iochannel_set_callback(u->io, io_callback, u); + + pa_log_info("Connection established, authenticating ..."); } -int pa__init(pa_core *c, pa_module*m) { +int pa__init(pa_module*m) { struct userdata *u = NULL; const char *p; pa_sample_spec ss; pa_modargs *ma = NULL; char *t; + const char *espeaker; - assert(c && m); + pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { pa_log("failed to parse module arguments"); goto fail; } - ss = c->default_sample_spec; + ss = m->core->default_sample_spec; if (pa_modargs_get_sample_spec(ma, &ss) < 0) { pa_log("invalid sample format specification"); goto fail; @@ -343,37 +526,62 @@ int pa__init(pa_core *c, pa_module*m) { goto fail; } - u = pa_xmalloc0(sizeof(struct userdata)); - u->core = c; + u = pa_xnew0(struct userdata, 1); + u->core = m->core; u->module = m; m->userdata = u; + u->fd = -1; + u->smoother = pa_smoother_new(PA_USEC_PER_SEC, PA_USEC_PER_SEC*2, TRUE); + pa_memchunk_reset(&u->memchunk); + u->offset = 0; + + pa_thread_mq_init(&u->thread_mq, m->core->mainloop); + u->rtpoll = pa_rtpoll_new(); + pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq); + u->rtpoll_item = NULL; + u->format = (ss.format == PA_SAMPLE_U8 ? ESD_BITS8 : ESD_BITS16) | (ss.channels == 2 ? ESD_STEREO : ESD_MONO); u->rate = ss.rate; - u->sink = NULL; - u->client = NULL; - u->io = NULL; + u->block_size = pa_usec_to_bytes(PA_USEC_PER_SEC/20, &ss); + u->read_data = u->write_data = NULL; u->read_index = u->write_index = u->read_length = u->write_length = 0; + u->state = STATE_AUTH; u->latency = 0; - if (!(u->sink = pa_sink_new(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, NULL))) { + if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, NULL))) { pa_log("failed to create sink."); goto fail; } - if (!(u->client = pa_socket_client_new_string(u->core->mainloop, p = pa_modargs_get_value(ma, "server", ESD_UNIX_SOCKET_NAME), ESD_DEFAULT_PORT))) { - pa_log("failed to connect to server."); + u->sink->parent.process_msg = sink_process_msg; + u->sink->userdata = u; + u->sink->flags = PA_SINK_LATENCY; + + pa_sink_set_module(u->sink, m); + pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); + pa_sink_set_rtpoll(u->sink, u->rtpoll); + + if (!(espeaker = getenv("ESPEAKER"))) + espeaker = ESD_UNIX_SOCKET_NAME; + + if (!(u->client = pa_socket_client_new_string(u->core->mainloop, p = pa_modargs_get_value(ma, "server", espeaker), ESD_DEFAULT_PORT))) { + pa_log("Failed to connect to server."); goto fail; } + + pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Esound sink '%s'", p)); + pa_xfree(t); + pa_socket_client_set_callback(u->client, on_connection, u); /* Prepare the initial request */ u->write_data = pa_xmalloc(u->write_length = ESD_KEY_LEN + sizeof(int32_t)); if (pa_authkey_load_auto(pa_modargs_get_value(ma, "cookie", ".esd_auth"), u->write_data, ESD_KEY_LEN) < 0) { - pa_log("failed to load cookie"); + pa_log("Failed to load cookie"); goto fail; } *(int32_t*) ((uint8_t*) u->write_data + ESD_KEY_LEN) = ESD_ENDIAN_KEY; @@ -381,19 +589,12 @@ int pa__init(pa_core *c, pa_module*m) { /* Reserve space for the response */ u->read_data = pa_xmalloc(u->read_length = sizeof(int32_t)); - u->sink->notify = notify_cb; - u->sink->get_latency = get_latency_cb; - u->sink->userdata = u; - pa_sink_set_owner(u->sink, m); - pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Esound sink '%s'", p)); - pa_xfree(t); - - u->memchunk.memblock = NULL; - u->memchunk.length = 0; - - u->defer_event = c->mainloop->defer_new(c->mainloop, defer_callback, u); - c->mainloop->defer_enable(u->defer_event, 0); + if (!(u->thread = pa_thread_new(thread_func, u))) { + pa_log("Failed to create thread."); + goto fail; + } + pa_sink_put(u->sink); pa_modargs_free(ma); @@ -403,20 +604,39 @@ fail: if (ma) pa_modargs_free(ma); - pa__done(c, m); + pa__done(m); return -1; } -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module*m) { struct userdata *u; - assert(c && m); + pa_assert(m); if (!(u = m->userdata)) return; - u->module = NULL; - cancel(u); + if (u->sink) + pa_sink_unlink(u->sink); + + if (u->thread) { + pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); + pa_thread_free(u->thread); + } + + pa_thread_mq_done(&u->thread_mq); + + if (u->sink) + pa_sink_unref(u->sink); + + if (u->io) + pa_iochannel_free(u->io); + + if (u->rtpoll_item) + pa_rtpoll_item_free(u->rtpoll_item); + + if (u->rtpoll) + pa_rtpoll_free(u->rtpoll); if (u->memchunk.memblock) pa_memblock_unref(u->memchunk.memblock); @@ -427,8 +647,11 @@ void pa__done(pa_core *c, pa_module*m) { pa_xfree(u->read_data); pa_xfree(u->write_data); - pa_xfree(u); -} - + if (u->smoother) + pa_smoother_free(u->smoother); + if (u->fd >= 0) + pa_close(u->fd); + pa_xfree(u); +} diff --git a/src/modules/module-hal-detect.c b/src/modules/module-hal-detect.c index 1f48a452..a8ca7df3 100644 --- a/src/modules/module-hal-detect.c +++ b/src/modules/module-hal-detect.c @@ -1,25 +1,25 @@ /* $Id$ */ /*** - This file is part of PulseAudio. + This file is part of PulseAudio. - Copyright 2006 Lennart Poettering - Copyright 2006 Shams E. King + Copyright 2006 Lennart Poettering + Copyright 2006 Shams E. King - PulseAudio is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published - by the Free Software Foundation; either version 2 of the License, - or (at your option) any later version. + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2 of the License, + or (at your option) any later version. - PulseAudio is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. - You should have received a copy of the GNU Lesser General Public License - along with PulseAudio; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - USA. + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. ***/ #ifdef HAVE_CONFIG_H @@ -27,7 +27,6 @@ #endif #include <stdio.h> -#include <assert.h> #include <unistd.h> #include <string.h> #include <stdlib.h> @@ -45,6 +44,9 @@ #include <pulsecore/hashmap.h> #include <pulsecore/idxset.h> #include <pulsecore/core-util.h> +#include <pulsecore/namereg.h> +#include <pulsecore/core-scache.h> +#include <pulsecore/modargs.h> #include <hal/libhal.h> @@ -54,40 +56,27 @@ PA_MODULE_AUTHOR("Shahms King") PA_MODULE_DESCRIPTION("Detect available audio hardware and load matching drivers") PA_MODULE_VERSION(PACKAGE_VERSION) - -typedef enum { -#ifdef HAVE_ALSA - CAP_ALSA, -#endif -#ifdef HAVE_OSS - CAP_OSS, -#endif - CAP_MAX -} capability_t; - -static const char* const capabilities[CAP_MAX] = { -#ifdef HAVE_ALSA - [CAP_ALSA] = "alsa", -#endif -#ifdef HAVE_OSS - [CAP_OSS] = "oss", +#if defined(HAVE_ALSA) && defined(HAVE_OSS) +PA_MODULE_USAGE("api=<alsa or oss>") +#elif defined(HAVE_ALSA) +PA_MODULE_USAGE("api=<alsa>") +#elif defined(HAVE_OSS) +PA_MODULE_USAGE("api=<oss>") #endif -}; struct device { uint32_t index; char *udi; + char *sink_name, *source_name; + int acl_race_fix; }; struct userdata { pa_core *core; - LibHalContext *ctx; - capability_t capability; - pa_dbus_connection *conn; + LibHalContext *context; + pa_dbus_connection *connection; pa_hashmap *devices; -#if defined(HAVE_ALSA) && defined(HAVE_OSS) - int use_oss; -#endif + const char *capability; }; struct timerdata { @@ -95,23 +84,30 @@ struct timerdata { char *udi; }; -static const char* get_capability_name(capability_t cap) { - if (cap >= CAP_MAX) - return NULL; - return capabilities[cap]; -} +#define CAPABILITY_ALSA "alsa" +#define CAPABILITY_OSS "oss" + +static const char* const valid_modargs[] = { + "api", + NULL +}; static void hal_device_free(struct device* d) { + pa_assert(d); + pa_xfree(d->udi); + pa_xfree(d->sink_name); + pa_xfree(d->source_name); pa_xfree(d); } static void hal_device_free_cb(void *d, PA_GCC_UNUSED void *data) { - hal_device_free((struct device*) d); + hal_device_free(d); } static const char *strip_udi(const char *udi) { const char *slash; + if ((slash = strrchr(udi, '/'))) return slash+1; @@ -119,6 +115,7 @@ static const char *strip_udi(const char *udi) { } #ifdef HAVE_ALSA + typedef enum { ALSA_TYPE_SINK, ALSA_TYPE_SOURCE, @@ -126,234 +123,297 @@ typedef enum { ALSA_TYPE_MAX } alsa_type_t; -static alsa_type_t hal_device_get_alsa_type(LibHalContext *ctx, const char *udi, - DBusError *error) -{ +static alsa_type_t hal_alsa_device_get_type(LibHalContext *context, const char *udi, DBusError *error) { char *type; alsa_type_t t; - type = libhal_device_get_property_string(ctx, udi, "alsa.type", error); - if (!type || dbus_error_is_set(error)) - return FALSE; + if (!(type = libhal_device_get_property_string(context, udi, "alsa.type", error))) + return ALSA_TYPE_OTHER; - if (!strcmp(type, "playback")) { + if (!strcmp(type, "playback")) t = ALSA_TYPE_SINK; - } else if (!strcmp(type, "capture")) { + else if (!strcmp(type, "capture")) t = ALSA_TYPE_SOURCE; - } else { + else t = ALSA_TYPE_OTHER; - } + libhal_free_string(type); return t; } -static int hal_device_get_alsa_card(LibHalContext *ctx, const char *udi, - DBusError *error) -{ - return libhal_device_get_property_int(ctx, udi, "alsa.card", error); -} +static int hal_alsa_device_is_modem(LibHalContext *context, const char *udi, DBusError *error) { + char *class; + int r; + + if (!(class = libhal_device_get_property_string(context, udi, "alsa.pcm_class", error))) + return 0; + + r = strcmp(class, "modem") == 0; + pa_xfree(class); -static int hal_device_get_alsa_device(LibHalContext *ctx, const char *udi, - DBusError *error) -{ - return libhal_device_get_property_int(ctx, udi, "alsa.device", error); + return r; } -static pa_module* hal_device_load_alsa(struct userdata *u, const char *udi, - DBusError *error) -{ - char args[128]; +static pa_module* hal_device_load_alsa(struct userdata *u, const char *udi, char **sink_name, char **source_name) { + char *args; alsa_type_t type; int device, card; const char *module_name; + DBusError error; + pa_module *m; - type = hal_device_get_alsa_type(u->ctx, udi, error); - if (dbus_error_is_set(error) || type == ALSA_TYPE_OTHER) - return NULL; + dbus_error_init(&error); - device = hal_device_get_alsa_device(u->ctx, udi, error); - if (dbus_error_is_set(error) || device != 0) - return NULL; + pa_assert(u); + pa_assert(sink_name); + pa_assert(source_name); - card = hal_device_get_alsa_card(u->ctx, udi, error); - if (dbus_error_is_set(error)) - return NULL; + *sink_name = *source_name = NULL; + + type = hal_alsa_device_get_type(u->context, udi, &error); + if (dbus_error_is_set(&error) || type == ALSA_TYPE_OTHER) + goto fail; + + device = libhal_device_get_property_int(u->context, udi, "alsa.device", &error); + if (dbus_error_is_set(&error) || device != 0) + goto fail; + + card = libhal_device_get_property_int(u->context, udi, "alsa.card", &error); + if (dbus_error_is_set(&error)) + goto fail; + + if (hal_alsa_device_is_modem(u->context, udi, &error)) + goto fail; if (type == ALSA_TYPE_SINK) { + *sink_name = pa_sprintf_malloc("alsa_output.%s", strip_udi(udi)); + module_name = "module-alsa-sink"; - snprintf(args, sizeof(args), "device=hw:%u sink_name=alsa_output.%s", card, strip_udi(udi)); + args = pa_sprintf_malloc("device=hw:%u sink_name=%s", card, *sink_name); } else { + *source_name = pa_sprintf_malloc("alsa_input.%s", strip_udi(udi)); + module_name = "module-alsa-source"; - snprintf(args, sizeof(args), "device=hw:%u source_name=alsa_input.%s", card, strip_udi(udi)); + args = pa_sprintf_malloc("device=hw:%u source_name=%s", card, *source_name); } pa_log_debug("Loading %s with arguments '%s'", module_name, args); - return pa_module_load(u->core, module_name, args); + m = pa_module_load(u->core, module_name, args); + + pa_xfree(args); + + if (!m) { + pa_xfree(*sink_name); + pa_xfree(*source_name); + *sink_name = *source_name = NULL; + } + + return m; + +fail: + if (dbus_error_is_set(&error)) { + pa_log_error("D-Bus error while parsing ALSA data: %s: %s", error.name, error.message); + dbus_error_free(&error); + } + + return NULL; } #endif #ifdef HAVE_OSS -static dbus_bool_t hal_device_is_oss_pcm(LibHalContext *ctx, const char *udi, - DBusError *error) -{ - dbus_bool_t rv = FALSE; - char* type, *device_file = NULL; + +static int hal_oss_device_is_pcm(LibHalContext *context, const char *udi, DBusError *error) { + char *class = NULL, *dev = NULL, *e; int device; + int r = 0; - type = libhal_device_get_property_string(ctx, udi, "oss.type", error); - if (!type || dbus_error_is_set(error)) - return FALSE; + class = libhal_device_get_property_string(context, udi, "oss.type", error); + if (dbus_error_is_set(error) || !class) + goto finish; - if (!strcmp(type, "pcm")) { - char *e; + if (strcmp(class, "pcm")) + goto finish; - device = libhal_device_get_property_int(ctx, udi, "oss.device", error); - if (dbus_error_is_set(error) || device != 0) - goto exit; + dev = libhal_device_get_property_string(context, udi, "oss.device_file", error); + if (dbus_error_is_set(error) || !dev) + goto finish; - device_file = libhal_device_get_property_string(ctx, udi, "oss.device_file", - error); - if (!device_file || dbus_error_is_set(error)) - goto exit; + if ((e = strrchr(dev, '/'))) + if (pa_startswith(e + 1, "audio")) + goto finish; - /* hack to ignore /dev/audio style devices */ - if ((e = strrchr(device_file, '/'))) - rv = !pa_startswith(e + 1, "audio"); - } + device = libhal_device_get_property_int(context, udi, "oss.device", error); + if (dbus_error_is_set(error) || device != 0) + goto finish; -exit: - libhal_free_string(type); - libhal_free_string(device_file); - return rv; + r = 1; + +finish: + + libhal_free_string(class); + libhal_free_string(dev); + + return r; } -static pa_module* hal_device_load_oss(struct userdata *u, const char *udi, - DBusError *error) -{ - char args[256]; +static pa_module* hal_device_load_oss(struct userdata *u, const char *udi, char **sink_name, char **source_name) { + char* args; char* device; + DBusError error; + pa_module *m; - if (!hal_device_is_oss_pcm(u->ctx, udi, error) || dbus_error_is_set(error)) - return NULL; + dbus_error_init(&error); - device = libhal_device_get_property_string(u->ctx, udi, "oss.device_file", - error); - if (!device || dbus_error_is_set(error)) - return NULL; + pa_assert(u); + pa_assert(sink_name); + pa_assert(source_name); + + *sink_name = *source_name = NULL; - snprintf(args, sizeof(args), "device=%s sink_name=oss_output.%s source_name=oss_input.%s", device, strip_udi(udi), strip_udi(udi)); + if (!hal_oss_device_is_pcm(u->context, udi, &error) || dbus_error_is_set(&error)) + goto fail; + + device = libhal_device_get_property_string(u->context, udi, "oss.device_file", &error); + if (!device || dbus_error_is_set(&error)) + goto fail; + + *sink_name = pa_sprintf_malloc("oss_output.%s", strip_udi(udi)); + *source_name = pa_sprintf_malloc("oss_input.%s", strip_udi(udi)); + + args = pa_sprintf_malloc("device=%s sink_name=%s source_name=%s", device, *sink_name, *source_name); libhal_free_string(device); pa_log_debug("Loading module-oss with arguments '%s'", args); + m = pa_module_load(u->core, "module-oss", args); + pa_xfree(args); + + if (!m) { + pa_xfree(*sink_name); + pa_xfree(*source_name); + *sink_name = *source_name = NULL; + } + + return m; + +fail: + if (dbus_error_is_set(&error)) { + pa_log_error("D-Bus error while parsing OSS data: %s: %s", error.name, error.message); + dbus_error_free(&error); + } - return pa_module_load(u->core, "module-oss", args); + return NULL; } #endif -static dbus_bool_t hal_device_add(struct userdata *u, const char *udi, - DBusError *error) -{ +static struct device* hal_device_add(struct userdata *u, const char *udi) { pa_module* m = NULL; struct device *d; + char *sink_name = NULL, *source_name = NULL; + + pa_assert(u); + pa_assert(u->capability); + pa_assert(!pa_hashmap_get(u->devices, udi)); - switch(u->capability) { #ifdef HAVE_ALSA - case CAP_ALSA: - m = hal_device_load_alsa(u, udi, error); - break; + if (strcmp(u->capability, CAPABILITY_ALSA) == 0) + m = hal_device_load_alsa(u, udi, &sink_name, &source_name); #endif #ifdef HAVE_OSS - case CAP_OSS: -#ifdef HAVE_ALSA - if (u->use_oss) -#endif - m = hal_device_load_oss(u, udi, error); - break; + if (strcmp(u->capability, CAPABILITY_OSS) == 0) + m = hal_device_load_oss(u, udi, &sink_name, &source_name); #endif - default: - assert(FALSE); /* never reached */ - break; - } - if (!m || dbus_error_is_set(error)) - return FALSE; + if (!m) + return NULL; d = pa_xnew(struct device, 1); + d->acl_race_fix = 0; d->udi = pa_xstrdup(udi); d->index = m->index; - + d->sink_name = sink_name; + d->source_name = source_name; pa_hashmap_put(u->devices, d->udi, d); - return TRUE; + return d; } -static int hal_device_add_all(struct userdata *u, capability_t capability) -{ +static int hal_device_add_all(struct userdata *u, const char *capability) { DBusError error; - int i,n,count; - dbus_bool_t r; + int i, n, count = 0; char** udis; - const char* cap = get_capability_name(capability); - assert(capability < CAP_MAX); + pa_assert(u); - pa_log_info("Trying capability %u (%s)", capability, cap); dbus_error_init(&error); - udis = libhal_find_device_by_capability(u->ctx, cap, &n, &error); + + if (u->capability && strcmp(u->capability, capability) != 0) + return 0; + + pa_log_info("Trying capability %s", capability); + + udis = libhal_find_device_by_capability(u->context, capability, &n, &error); if (dbus_error_is_set(&error)) { - pa_log_error("Error finding devices: %s: %s", error.name, - error.message); + pa_log_error("Error finding devices: %s: %s", error.name, error.message); dbus_error_free(&error); return -1; } - count = 0; - u->capability = capability; - for (i = 0; i < n; ++i) { - r = hal_device_add(u, udis[i], &error); - if (dbus_error_is_set(&error)) { - pa_log_error("Error adding device: %s: %s", error.name, - error.message); - dbus_error_free(&error); - count = -1; - break; + + if (n > 0) { + u->capability = capability; + + for (i = 0; i < n; i++) { + struct device *d; + + if (!(d = hal_device_add(u, udis[i]))) + pa_log_debug("Not loaded device %s", udis[i]); + else { + if (d->sink_name) + pa_scache_play_item_by_name(u->core, "pulse-coldplug", d->sink_name, PA_VOLUME_NORM, 0); + count++; + } } - if (r) - ++count; } libhal_free_string_array(udis); return count; } -static dbus_bool_t device_has_capability(LibHalContext *ctx, const char *udi, - const char* cap, DBusError *error) -{ +static dbus_bool_t device_has_capability(LibHalContext *context, const char *udi, const char* cap, DBusError *error){ dbus_bool_t has_prop; - has_prop = libhal_device_property_exists(ctx, udi, "info.capabilities", - error); + + has_prop = libhal_device_property_exists(context, udi, "info.capabilities", error); if (!has_prop || dbus_error_is_set(error)) return FALSE; - return libhal_device_query_capability(ctx, udi, cap, error); + return libhal_device_query_capability(context, udi, cap, error); } -static void device_added_time_cb(pa_mainloop_api *ea, pa_time_event *ev, - const struct timeval *tv, void *userdata) -{ +static void device_added_time_cb(pa_mainloop_api *ea, pa_time_event *ev, const struct timeval *tv, void *userdata) { DBusError error; - struct timerdata *td = (struct timerdata*) userdata; + struct timerdata *td = userdata; dbus_error_init(&error); - if (libhal_device_exists(td->u->ctx, td->udi, &error)) - hal_device_add(td->u, td->udi, &error); - if (dbus_error_is_set(&error)) { - pa_log_error("Error adding device: %s: %s", error.name, - error.message); - dbus_error_free(&error); + if (!pa_hashmap_get(td->u->devices, td->udi)) { + int b; + struct device *d; + + b = libhal_device_exists(td->u->context, td->udi, &error); + + if (dbus_error_is_set(&error)) { + pa_log_error("Error adding device: %s: %s", error.name, error.message); + dbus_error_free(&error); + } else if (b) { + if (!(d = hal_device_add(td->u, td->udi))) + pa_log_debug("Not loaded device %s", td->udi); + else { + if (d->sink_name) + pa_scache_play_item_by_name(td->u->core, "pulse-hotplug", d->sink_name, PA_VOLUME_NORM, 0); + } + } } pa_xfree(td->udi); @@ -361,28 +421,68 @@ static void device_added_time_cb(pa_mainloop_api *ea, pa_time_event *ev, ea->time_free(ev); } -static void device_added_cb(LibHalContext *ctx, const char *udi) -{ +static void device_added_cb(LibHalContext *context, const char *udi) { DBusError error; struct timeval tv; - dbus_bool_t has_cap; struct timerdata *t; - struct userdata *u = (struct userdata*) libhal_ctx_get_user_data(ctx); - const char* cap = get_capability_name(u->capability); + struct userdata *u; + int good = 0; + + pa_assert_se(u = libhal_ctx_get_user_data(context)); + + if (pa_hashmap_get(u->devices, udi)) + return; pa_log_debug("HAL Device added: %s", udi); dbus_error_init(&error); - has_cap = device_has_capability(ctx, udi, cap, &error); - if (dbus_error_is_set(&error)) { - pa_log_error("Error getting capability: %s: %s", error.name, - error.message); - dbus_error_free(&error); - return; + + if (u->capability) { + + good = device_has_capability(context, udi, u->capability, &error); + + if (dbus_error_is_set(&error)) { + pa_log_error("Error getting capability: %s: %s", error.name, error.message); + dbus_error_free(&error); + return; + } + + } else { + +#ifdef HAVE_ALSA + good = device_has_capability(context, udi, CAPABILITY_ALSA, &error); + + if (dbus_error_is_set(&error)) { + pa_log_error("Error getting capability: %s: %s", error.name, error.message); + dbus_error_free(&error); + return; + } + + if (good) + u->capability = CAPABILITY_ALSA; +#endif +#if defined(HAVE_OSS) && defined(HAVE_ALSA) + if (!good) { +#endif +#ifdef HAS_OSS + good = device_has_capability(context, udi, CAPABILITY_OSS, &error); + + if (dbus_error_is_set(&error)) { + pa_log_error("Error getting capability: %s: %s", error.name, error.message); + dbus_error_free(&error); + return; + } + + if (good) + u->capability = CAPABILITY_OSS; + +#endif +#if defined(HAVE_OSS) && defined(HAVE_ALSA) + } +#endif } - /* skip it */ - if (!has_cap) + if (!good) return; /* actually add the device 1/2 second later */ @@ -392,187 +492,359 @@ static void device_added_cb(LibHalContext *ctx, const char *udi) pa_gettimeofday(&tv); pa_timeval_add(&tv, 500000); - u->core->mainloop->time_new(u->core->mainloop, &tv, - device_added_time_cb, t); + u->core->mainloop->time_new(u->core->mainloop, &tv, device_added_time_cb, t); } -static void device_removed_cb(LibHalContext* ctx, const char *udi) -{ +static void device_removed_cb(LibHalContext* context, const char *udi) { struct device *d; - struct userdata *u = (struct userdata*) libhal_ctx_get_user_data(ctx); + struct userdata *u; + + pa_assert_se(u = libhal_ctx_get_user_data(context)); pa_log_debug("Device removed: %s", udi); + if ((d = pa_hashmap_remove(u->devices, udi))) { pa_module_unload_by_index(u->core, d->index); hal_device_free(d); } } -static void new_capability_cb(LibHalContext *ctx, const char *udi, - const char* capability) -{ - struct userdata *u = (struct userdata*) libhal_ctx_get_user_data(ctx); - const char* capname = get_capability_name(u->capability); +static void new_capability_cb(LibHalContext *context, const char *udi, const char* capability) { + struct userdata *u; + + pa_assert_se(u = libhal_ctx_get_user_data(context)); - if (capname && !strcmp(capname, capability)) { + if (!u->capability || strcmp(u->capability, capability) == 0) /* capability we care about, pretend it's a new device */ - device_added_cb(ctx, udi); - } + device_added_cb(context, udi); } -static void lost_capability_cb(LibHalContext *ctx, const char *udi, - const char* capability) -{ - struct userdata *u = (struct userdata*) libhal_ctx_get_user_data(ctx); - const char* capname = get_capability_name(u->capability); +static void lost_capability_cb(LibHalContext *context, const char *udi, const char* capability) { + struct userdata *u; - if (capname && !strcmp(capname, capability)) { - /* capability we care about, pretend it was removed */ - device_removed_cb(ctx, udi); - } -} + pa_assert_se(u = libhal_ctx_get_user_data(context)); -#if 0 -static void property_modified_cb(LibHalContext *ctx, const char *udi, - const char* key, - dbus_bool_t is_removed, - dbus_bool_t is_added) -{ + if (u->capability && strcmp(u->capability, capability) == 0) + /* capability we care about, pretend it was removed */ + device_removed_cb(context, udi); } -#endif -static void pa_hal_context_free(LibHalContext* hal_ctx) -{ +static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, void *userdata) { + struct userdata*u = userdata; DBusError error; + pa_assert(bus); + pa_assert(message); + pa_assert(u); + dbus_error_init(&error); - libhal_ctx_shutdown(hal_ctx, &error); - libhal_ctx_free(hal_ctx); - if (dbus_error_is_set(&error)) { - dbus_error_free(&error); + pa_log_debug("dbus: interface=%s, path=%s, member=%s\n", + dbus_message_get_interface(message), + dbus_message_get_path(message), + dbus_message_get_member(message)); + + if (dbus_message_is_signal(message, "org.freedesktop.Hal.Device.AccessControl", "ACLAdded") || + dbus_message_is_signal(message, "org.freedesktop.Hal.Device.AccessControl", "ACLRemoved")) { + uint32_t uid; + int suspend = strcmp(dbus_message_get_member(message), "ACLRemoved") == 0; + + if (!dbus_message_get_args(message, &error, DBUS_TYPE_UINT32, &uid, DBUS_TYPE_INVALID) || dbus_error_is_set(&error)) { + pa_log_error("Failed to parse ACL message: %s: %s", error.name, error.message); + goto finish; + } + + if (uid == getuid() || uid == geteuid()) { + struct device *d; + const char *udi; + + udi = dbus_message_get_path(message); + + if ((d = pa_hashmap_get(u->devices, udi))) { + int send_acl_race_fix_message = 0; + + d->acl_race_fix = 0; + + if (d->sink_name) { + pa_sink *sink; + + if ((sink = pa_namereg_get(u->core, d->sink_name, PA_NAMEREG_SINK, 0))) { + int prev_suspended = pa_sink_get_state(sink) == PA_SINK_SUSPENDED; + + if (prev_suspended && !suspend) { + /* resume */ + if (pa_sink_suspend(sink, 0) >= 0) + pa_scache_play_item_by_name(u->core, "pulse-access", d->sink_name, PA_VOLUME_NORM, 0); + else + d->acl_race_fix = 1; + + } else if (!prev_suspended && suspend) { + /* suspend */ + if (pa_sink_suspend(sink, 1) >= 0) + send_acl_race_fix_message = 1; + } + } + } + + if (d->source_name) { + pa_source *source; + + if ((source = pa_namereg_get(u->core, d->source_name, PA_NAMEREG_SOURCE, 0))) { + int prev_suspended = pa_source_get_state(source) == PA_SOURCE_SUSPENDED; + + if (prev_suspended && !suspend) { + /* resume */ + if (pa_source_suspend(source, 0) < 0) + d->acl_race_fix = 1; + + } else if (!prev_suspended && suspend) { + /* suspend */ + if (pa_source_suspend(source, 0) >= 0) + send_acl_race_fix_message = 1; + } + } + } + + if (send_acl_race_fix_message) { + DBusMessage *msg; + msg = dbus_message_new_signal(udi, "org.pulseaudio.Server", "DirtyGiveUpMessage"); + dbus_connection_send(pa_dbus_connection_get(u->connection), msg, NULL); + dbus_message_unref(msg); + } + + } else if (!suspend) + device_added_cb(u->context, udi); + } + + } else if (dbus_message_is_signal(message, "org.pulseaudio.Server", "DirtyGiveUpMessage")) { + /* We use this message to avoid a dirty race condition when we + get an ACLAdded message before the previously owning PA + sever has closed the device. We can remove this as + soon as HAL learns frevoke() */ + + const char *udi; + struct device *d; + + udi = dbus_message_get_path(message); + + if ((d = pa_hashmap_get(u->devices, udi)) && d->acl_race_fix) { + pa_log_debug("Got dirty give up message for '%s', trying resume ...", udi); + + d->acl_race_fix = 0; + + if (d->sink_name) { + pa_sink *sink; + + if ((sink = pa_namereg_get(u->core, d->sink_name, PA_NAMEREG_SINK, 0))) { + + int prev_suspended = pa_sink_get_state(sink) == PA_SINK_SUSPENDED; + + if (prev_suspended) { + /* resume */ + if (pa_sink_suspend(sink, 0) >= 0) + pa_scache_play_item_by_name(u->core, "pulse-access", d->sink_name, PA_VOLUME_NORM, 0); + } + } + } + + if (d->source_name) { + pa_source *source; + + if ((source = pa_namereg_get(u->core, d->source_name, PA_NAMEREG_SOURCE, 0))) { + + int prev_suspended = pa_source_get_state(source) == PA_SOURCE_SUSPENDED; + + if (prev_suspended) + pa_source_suspend(source, 0); + } + } + + } else + /* Yes, we don't check the UDI for validity, but hopefully HAL will */ + device_added_cb(u->context, udi); } + +finish: + dbus_error_free(&error); + + return DBUS_HANDLER_RESULT_HANDLED; } -static void userdata_free(struct userdata *u) { - pa_hal_context_free(u->ctx); - /* free the devices with the hashmap */ - pa_hashmap_free(u->devices, hal_device_free_cb, NULL); - pa_dbus_connection_unref(u->conn); - pa_xfree(u); +static void hal_context_free(LibHalContext* hal_context) { + DBusError error; + + dbus_error_init(&error); + + libhal_ctx_shutdown(hal_context, &error); + libhal_ctx_free(hal_context); + + dbus_error_free(&error); } -static LibHalContext* pa_hal_context_new(pa_core* c, DBusConnection *conn) -{ +static LibHalContext* hal_context_new(pa_core* c, DBusConnection *conn) { DBusError error; - LibHalContext *hal_ctx = NULL; + LibHalContext *hal_context = NULL; dbus_error_init(&error); - if (!(hal_ctx = libhal_ctx_new())) { + + if (!(hal_context = libhal_ctx_new())) { pa_log_error("libhal_ctx_new() failed"); goto fail; } - if (!libhal_ctx_set_dbus_connection(hal_ctx, conn)) { - pa_log_error("Error establishing DBUS connection: %s: %s", - error.name, error.message); + if (!libhal_ctx_set_dbus_connection(hal_context, conn)) { + pa_log_error("Error establishing DBUS connection: %s: %s", error.name, error.message); goto fail; } - if (!libhal_ctx_init(hal_ctx, &error)) { - pa_log_error("Couldn't connect to hald: %s: %s", - error.name, error.message); + if (!libhal_ctx_init(hal_context, &error)) { + pa_log_error("Couldn't connect to hald: %s: %s", error.name, error.message); goto fail; } - return hal_ctx; + return hal_context; fail: - if (hal_ctx) - pa_hal_context_free(hal_ctx); + if (hal_context) + hal_context_free(hal_context); - if (dbus_error_is_set(&error)) - dbus_error_free(&error); + dbus_error_free(&error); return NULL; } -int pa__init(pa_core *c, pa_module*m) { +int pa__init(pa_module*m) { DBusError error; pa_dbus_connection *conn; struct userdata *u = NULL; - LibHalContext *hal_ctx = NULL; + LibHalContext *hal_context = NULL; int n = 0; + pa_modargs *ma; + const char *api; - assert(c); - assert(m); + pa_assert(m); dbus_error_init(&error); - if (!(conn = pa_dbus_bus_get(c, DBUS_BUS_SYSTEM, &error))) { - pa_log_error("Unable to contact DBUS system bus: %s: %s", - error.name, error.message); - dbus_error_free(&error); - return -1; + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments"); + goto fail; } - if (!(hal_ctx = pa_hal_context_new(c, pa_dbus_connection_get(conn)))) { + if ((api = pa_modargs_get_value(ma, "api", NULL))) { + int good = 0; + +#ifdef HAVE_ALSA + if (strcmp(api, CAPABILITY_ALSA) == 0) { + good = 1; + api = CAPABILITY_ALSA; + } +#endif +#ifdef HAVE_OSS + if (strcmp(api, CAPABILITY_OSS) == 0) { + good = 1; + api = CAPABILITY_OSS; + } +#endif + + if (!good) { + pa_log_error("Invalid API specification."); + goto fail; + } + } + + if (!(conn = pa_dbus_bus_get(m->core, DBUS_BUS_SYSTEM, &error)) || dbus_error_is_set(&error)) { + if (conn) + pa_dbus_connection_unref(conn); + pa_log_error("Unable to contact DBUS system bus: %s: %s", error.name, error.message); + goto fail; + } + + if (!(hal_context = hal_context_new(m->core, pa_dbus_connection_get(conn)))) { /* pa_hal_context_new() logs appropriate errors */ - return -1; + pa_dbus_connection_unref(conn); + goto fail; } u = pa_xnew(struct userdata, 1); - u->core = c; - u->ctx = hal_ctx; - u->conn = conn; - u->devices = pa_hashmap_new(pa_idxset_string_hash_func, - pa_idxset_string_compare_func); - m->userdata = (void*) u; + u->core = m->core; + u->context = hal_context; + u->connection = conn; + u->devices = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + u->capability = api; + m->userdata = u; #ifdef HAVE_ALSA - n = hal_device_add_all(u, CAP_ALSA); + n = hal_device_add_all(u, CAPABILITY_ALSA); #endif #if defined(HAVE_ALSA) && defined(HAVE_OSS) - u->use_oss = 0; - - if (n <= 0) { + if (n <= 0) #endif #ifdef HAVE_OSS - n += hal_device_add_all(u, CAP_OSS); + n += hal_device_add_all(u, CAPABILITY_OSS); #endif -#if defined(HAVE_ALSA) && defined(HAVE_OSS) - /* We found something with OSS, but didn't find anything with - * ALSA. Then let's use only OSS from now on. */ - if (n > 0) - u->use_oss = 1; + libhal_ctx_set_user_data(hal_context, u); + libhal_ctx_set_device_added(hal_context, device_added_cb); + libhal_ctx_set_device_removed(hal_context, device_removed_cb); + libhal_ctx_set_device_new_capability(hal_context, new_capability_cb); + libhal_ctx_set_device_lost_capability(hal_context, lost_capability_cb); + + if (!libhal_device_property_watch_all(hal_context, &error)) { + pa_log_error("Error monitoring device list: %s: %s", error.name, error.message); + goto fail; } -#endif - libhal_ctx_set_user_data(hal_ctx, u); - libhal_ctx_set_device_added(hal_ctx, device_added_cb); - libhal_ctx_set_device_removed(hal_ctx, device_removed_cb); - libhal_ctx_set_device_new_capability(hal_ctx, new_capability_cb); - libhal_ctx_set_device_lost_capability(hal_ctx, lost_capability_cb); - /*libhal_ctx_set_device_property_modified(hal_ctx, property_modified_cb);*/ + if (!dbus_connection_add_filter(pa_dbus_connection_get(conn), filter_cb, u, NULL)) { + pa_log_error("Failed to add filter function"); + goto fail; + } - dbus_error_init(&error); - if (!libhal_device_property_watch_all(hal_ctx, &error)) { - pa_log_error("error monitoring device list: %s: %s", - error.name, error.message); - dbus_error_free(&error); - userdata_free(u); - return -1; + dbus_bus_add_match(pa_dbus_connection_get(conn), "type='signal',sender='org.freedesktop.Hal', interface='org.freedesktop.Hal.Device.AccessControl'", &error); + if (dbus_error_is_set(&error)) { + pa_log_error("Unable to subscribe to HAL ACL signals: %s: %s", error.name, error.message); + goto fail; + } + + dbus_bus_add_match(pa_dbus_connection_get(conn), "type='signal',interface='org.pulseaudio.Server'", &error); + if (dbus_error_is_set(&error)) { + pa_log_error("Unable to subscribe to PulseAudio signals: %s: %s", error.name, error.message); + goto fail; } - pa_log_info("loaded %i modules.", n); + pa_log_info("Loaded %i modules.", n); + + pa_modargs_free(ma); return 0; + +fail: + if (ma) + pa_modargs_free(ma); + + dbus_error_free(&error); + pa__done(m); + + return -1; } -void pa__done(PA_GCC_UNUSED pa_core *c, pa_module *m) { - assert (c && m); +void pa__done(pa_module *m) { + struct userdata *u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->context) + hal_context_free(u->context); - /* free the user data */ - userdata_free(m->userdata); + if (u->devices) + pa_hashmap_free(u->devices, hal_device_free_cb, NULL); + + if (u->connection) + pa_dbus_connection_unref(u->connection); + + pa_xfree(u); } diff --git a/src/modules/module-jack-sink.c b/src/modules/module-jack-sink.c index a40ebe29..5019d656 100644 --- a/src/modules/module-jack-sink.c +++ b/src/modules/module-jack-sink.c @@ -3,7 +3,7 @@ /*** This file is part of PulseAudio. - Copyright 2006, 2007 Lennart Poettering and Tanu Kaskinen + Copyright 2006 Lennart Poettering PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published @@ -25,858 +25,431 @@ #include <config.h> #endif -#include <pthread.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <stdio.h> #include <assert.h> #include <errno.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> +#include <limits.h> #include <jack/jack.h> -#include <jack/ringbuffer.h> -#include <jack/types.h> -#include <pulse/mainloop-api.h> -#include <pulse/sample.h> -#include <pulse/channelmap.h> #include <pulse/xmalloc.h> +#include <pulsecore/core-error.h> #include <pulsecore/sink.h> #include <pulsecore/module.h> -#include <pulsecore/core.h> -#include <pulsecore/log.h> #include <pulsecore/core-util.h> -#include <pulsecore/core-error.h> -#include <pulsecore/pipe.h> #include <pulsecore/modargs.h> -#include <pulsecore/strbuf.h> +#include <pulsecore/log.h> +#include <pulsecore/thread.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/rtpoll.h> +#include <pulsecore/sample-util.h> #include "module-jack-sink-symdef.h" -PA_MODULE_AUTHOR("Lennart Poettering & Tanu Kaskinen") -PA_MODULE_DESCRIPTION("Jack Sink") +/* General overview: + * + * Because JACK has a very unflexible event loop management, which + * doesn't allow us to add our own event sources to the event thread + * we cannot use the JACK real-time thread for dispatching our PA + * work. Instead, we run an additional RT thread which does most of + * the PA handling, and have the JACK RT thread request data from it + * via pa_asyncmsgq. The cost is an additional context switch which + * should hopefully not be that expensive if RT scheduling is + * enabled. A better fix would only be possible with additional event + * source support in JACK. + */ + +PA_MODULE_AUTHOR("Lennart Poettering") +PA_MODULE_DESCRIPTION("JACK Sink") PA_MODULE_VERSION(PACKAGE_VERSION) PA_MODULE_USAGE( "sink_name=<name of sink> " "server_name=<jack server name> " "client_name=<jack client name> " "channels=<number of channels> " - "connect=<connect ports automatically?> " - "buffersize=<intermediate buffering in frames> " + "connect=<connect ports?> " "channel_map=<channel map>") #define DEFAULT_SINK_NAME "jack_out" -#define DEFAULT_CLIENT_NAME "PulseAudio(output)" -#define DEFAULT_RINGBUFFER_SIZE 4096 - struct userdata { + pa_core *core; + pa_module *module; pa_sink *sink; unsigned channels; - unsigned frame_size; - jack_port_t* j_ports[PA_CHANNELS_MAX]; - jack_client_t *j_client; + jack_port_t* port[PA_CHANNELS_MAX]; + jack_client_t *client; - jack_nframes_t j_buffersize; + void *buffer[PA_CHANNELS_MAX]; - /* For avoiding j_buffersize changes at a wrong moment. */ - pthread_mutex_t buffersize_mutex; + pa_thread_mq thread_mq; + pa_asyncmsgq *jack_msgq; + pa_rtpoll *rtpoll; + pa_rtpoll_item *rtpoll_item; - /* The intermediate store where the pulse side writes to and the jack side - reads from. */ - jack_ringbuffer_t* ringbuffer; - - /* For signaling when there's room in the ringbuffer. */ - pthread_mutex_t cond_mutex; - pthread_cond_t ringbuffer_cond; + pa_thread *thread; - pthread_t filler_thread; /* Keeps the ringbuffer filled. */ + jack_nframes_t frames_in_buffer; + jack_nframes_t saved_frame_time; + pa_bool_t saved_frame_time_valid; +}; - int ringbuffer_is_full; - int filler_thread_is_running; - int quit_requested; +static const char* const valid_modargs[] = { + "sink_name", + "server_name", + "client_name", + "channels", + "connect", + "channel_map", + NULL +}; - int pipe_fd_type; - int pipe_fds[2]; - pa_io_event *io_event; +enum { + SINK_MESSAGE_RENDER = PA_SINK_MESSAGE_MAX, + SINK_MESSAGE_ON_SHUTDOWN }; +static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *memchunk) { + struct userdata *u = PA_SINK(o)->userdata; -struct options { - char* sink_name; - int sink_name_given; + switch (code) { - char* server_name; /* May be NULL */ - int server_name_given; + case SINK_MESSAGE_RENDER: - char* client_name; - int client_name_given; + /* Handle the request from the JACK thread */ - unsigned channels; - int channels_given; + if (u->sink->thread_info.state == PA_SINK_RUNNING) { + pa_memchunk chunk; + size_t nbytes; + void *p; - int connect; - int connect_given; + pa_assert(offset > 0); + nbytes = offset * pa_frame_size(&u->sink->sample_spec); - unsigned buffersize; - int buffersize_given; + pa_sink_render_full(u->sink, nbytes, &chunk); - pa_channel_map map; - int map_given; -}; + p = (uint8_t*) pa_memblock_acquire(chunk.memblock) + chunk.index; + pa_deinterleave(p, u->buffer, u->channels, sizeof(float), offset); + pa_memblock_release(chunk.memblock); + pa_memblock_unref(chunk.memblock); + } else { + unsigned c; + pa_sample_spec ss; -static const char* const valid_modargs[] = { - "sink_name", - "server_name", - "client_name", - "channels", - "connect", - "buffersize", - "channel_map", - NULL -}; + /* Humm, we're not RUNNING, hence let's write some silence */ + ss = u->sink->sample_spec; + ss.channels = 1; -/* Initialization functions. */ -static int parse_options(struct options* o, const char* argument); -static void set_default_channels(pa_module* self, struct options* o); -static int create_sink(pa_module* self, struct options *o); -static void connect_ports(pa_module* self); -static int start_filling_ringbuffer(pa_module* self); + for (c = 0; c < u->channels; c++) + pa_silence_memory(u->buffer[c], offset * pa_sample_size(&ss), &ss); + } -/* Various callbacks. */ -static void jack_error_func(const char* t); -static pa_usec_t sink_get_latency_cb(pa_sink* s); -static int jack_process(jack_nframes_t nframes, void* arg); -static int jack_blocksize_cb(jack_nframes_t nframes, void* arg); -static void jack_shutdown(void* arg); -static void io_event_cb(pa_mainloop_api* m, pa_io_event* e, int fd, - pa_io_event_flags_t flags, void* userdata); + u->frames_in_buffer = offset; + u->saved_frame_time = * (jack_nframes_t*) data; + u->saved_frame_time_valid = TRUE; -/* The ringbuffer filler thread runs in this function. */ -static void* fill_ringbuffer(void* arg); + return 0; -/* request_render asks asynchronously the mainloop to call io_event_cb. */ -static void request_render(struct userdata* u); + case SINK_MESSAGE_ON_SHUTDOWN: + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + return 0; + case PA_SINK_MESSAGE_GET_LATENCY: { + jack_nframes_t l, ft, d; + size_t n; -int pa__init(pa_core* c, pa_module* self) { - struct userdata* u = NULL; - struct options o; - unsigned i; - - assert(c); - assert(self); - - o.sink_name = NULL; - o.server_name = NULL; - o.client_name = NULL; - - self->userdata = pa_xnew0(struct userdata, 1); - u = self->userdata; - - u->pipe_fds[0] = u->pipe_fds[1] = -1; - u->pipe_fd_type = 0; - u->ringbuffer_is_full = 0; - u->filler_thread_is_running = 0; - u->quit_requested = 0; - pthread_mutex_init(&u->buffersize_mutex, NULL); - pthread_mutex_init(&u->cond_mutex, NULL); - pthread_cond_init(&u->ringbuffer_cond, NULL); - - if (parse_options(&o, self->argument) != 0) - goto fail; - - jack_set_error_function(jack_error_func); - - if (!(u->j_client = jack_client_open( - o.client_name, - o.server_name ? JackServerName : JackNullOption, - NULL, o.server_name))) { - pa_log_error("jack_client_open() failed."); - goto fail; - } - pa_log_info("Successfully connected as '%s'", - jack_get_client_name(u->j_client)); - - if (!o.channels_given) - set_default_channels(self, &o); - - u->channels = o.channels; - - if (!o.map_given) - pa_channel_map_init_auto(&o.map, u->channels, PA_CHANNEL_MAP_ALSA); - - for (i = 0; i < u->channels; i++) { - char* port_name = pa_sprintf_malloc( - "out_%i:%s", i+1, - pa_channel_position_to_string(o.map.map[i])); - - if (!(u->j_ports[i] = jack_port_register( - u->j_client, port_name, - JACK_DEFAULT_AUDIO_TYPE, - JackPortIsOutput|JackPortIsTerminal, 0))) { - pa_log("jack_port_register() failed."); - goto fail; + /* This is the "worst-case" latency */ + l = jack_port_get_total_latency(u->client, u->port[0]) + u->frames_in_buffer; + + if (u->saved_frame_time_valid) { + /* Adjust the worst case latency by the time that + * passed since we last handed data to JACK */ + + ft = jack_frame_time(u->client); + d = ft > u->saved_frame_time ? ft - u->saved_frame_time : 0; + l = l > d ? l - d : 0; + } + + /* Convert it to usec */ + n = l * pa_frame_size(&u->sink->sample_spec); + *((pa_usec_t*) data) = pa_bytes_to_usec(n, &u->sink->sample_spec); + + return 0; } - - pa_xfree(port_name); } - - if (pipe(u->pipe_fds) < 0) { - pa_log("pipe() failed: %s", pa_cstrerror(errno)); - goto fail; - } - pa_make_nonblock_fd(u->pipe_fds[1]); - - if (create_sink(self, &o) != 0) - goto fail; - u->frame_size = pa_frame_size(&u->sink->sample_spec); - u->j_buffersize = jack_get_buffer_size(u->j_client); - - /* If the ringbuffer size were equal to the jack buffer size, a full block - would never fit in the ringbuffer, because the ringbuffer can never be - totally full: one slot is always wasted. */ - if (o.buffersize <= u->j_buffersize) { - o.buffersize = u->j_buffersize + 1; - } - /* The actual ringbuffer size will be rounded up to the nearest power of - two. */ - if (!(u->ringbuffer = jack_ringbuffer_create( - o.buffersize * u->frame_size))) { - pa_log("jack_ringbuffer_create() failed."); - goto fail; - } - assert((u->ringbuffer->size % sizeof(float)) == 0); - pa_log_info("buffersize is %u frames (%u samples, %u bytes).", - u->ringbuffer->size / u->frame_size, - u->ringbuffer->size / sizeof(float), - u->ringbuffer->size); - - jack_set_process_callback(u->j_client, jack_process, u); - jack_set_buffer_size_callback(u->j_client, jack_blocksize_cb, u); - jack_on_shutdown(u->j_client, jack_shutdown, u); - - if (jack_activate(u->j_client)) { - pa_log("jack_activate() failed."); - goto fail; - } + return pa_sink_process_msg(o, code, data, offset, memchunk); +} - if (o.connect) - connect_ports(self); +static int jack_process(jack_nframes_t nframes, void *arg) { + struct userdata *u = arg; + unsigned c; + jack_nframes_t frame_time; + pa_assert(u); - u->io_event = c->mainloop->io_new(c->mainloop, u->pipe_fds[0], - PA_IO_EVENT_INPUT, io_event_cb, self); - - if (start_filling_ringbuffer(self) != 0) - goto fail; + /* We just forward the request to our other RT thread */ - pa_xfree(o.sink_name); - pa_xfree(o.server_name); - pa_xfree(o.client_name); - - return 0; + for (c = 0; c < u->channels; c++) + pa_assert_se(u->buffer[c] = jack_port_get_buffer(u->port[c], nframes)); -fail: - pa_xfree(o.sink_name); - pa_xfree(o.server_name); - pa_xfree(o.client_name); - pa__done(c, self); + frame_time = jack_frame_time(u->client); - return -1; + pa_assert_se(pa_asyncmsgq_send(u->jack_msgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_RENDER, &frame_time, nframes, NULL) == 0); + return 0; } +static void thread_func(void *userdata) { + struct userdata *u = userdata; -static int parse_options(struct options* o, const char* argument) { - pa_modargs *ma = NULL; - const char* arg_val; - pa_strbuf* strbuf; - - assert(o); + pa_assert(u); - if (!(ma = pa_modargs_new(argument, valid_modargs))) { - pa_log_error("Failed to parse module arguments."); - goto fail; - } + pa_log_debug("Thread starting up"); - strbuf = pa_strbuf_new(); - if ((arg_val = pa_modargs_get_value(ma, "sink_name", NULL))) { - pa_strbuf_puts(strbuf, arg_val); - o->sink_name = pa_strbuf_tostring(strbuf); - o->sink_name_given = 1; - } else { - pa_strbuf_puts(strbuf, DEFAULT_SINK_NAME); - o->sink_name = pa_strbuf_tostring(strbuf); - o->sink_name_given = 0; - } - pa_strbuf_free(strbuf); - - strbuf = pa_strbuf_new(); - if ((arg_val = pa_modargs_get_value(ma, "server_name", NULL))) { - pa_strbuf_puts(strbuf, arg_val); - o->server_name = pa_strbuf_tostring(strbuf); - o->server_name_given = 1; - } else { - o->server_name = NULL; - o->server_name_given = 0; - } - pa_strbuf_free(strbuf); - - strbuf = pa_strbuf_new(); - if ((arg_val = pa_modargs_get_value(ma, "client_name", NULL))) { - pa_strbuf_puts(strbuf, arg_val); - o->client_name = pa_strbuf_tostring(strbuf); - o->client_name_given = 1; - } else { - pa_strbuf_puts(strbuf, DEFAULT_CLIENT_NAME); - o->client_name = pa_strbuf_tostring(strbuf); - o->client_name_given = 1; - } - pa_strbuf_free(strbuf); - - if (pa_modargs_get_value(ma, "channels", NULL)) { - o->channels_given = 1; - if (pa_modargs_get_value_u32(ma, "channels", &o->channels) < 0 || - o->channels == 0 || - o->channels >= PA_CHANNELS_MAX) { - pa_log_error("Failed to parse the \"channels\" argument."); - goto fail; - } - } else { - o->channels = 0; /* The actual default value is the number of physical - input ports in jack (unknown at the moment), or if - that's zero, then the default_sample_spec.channels - of the core. */ - o->channels_given = 0; - } + if (u->core->high_priority) + pa_make_realtime(); - if (pa_modargs_get_value(ma, "connect", NULL)) { - o->connect_given = 1; - if (pa_modargs_get_value_boolean(ma, "connect", &o->connect) < 0) { - pa_log_error("Failed to parse the \"connect\" argument."); - goto fail; - } - } else { - o->connect = 1; - o->connect_given = 0; - } + pa_thread_mq_install(&u->thread_mq); + pa_rtpoll_install(u->rtpoll); - if (pa_modargs_get_value(ma, "buffersize", NULL)) { - o->buffersize_given = 1; - if (pa_modargs_get_value_u32(ma, "buffersize", &o->buffersize) < 0) { - pa_log_error("Failed to parse the \"buffersize\" argument."); - goto fail; - } - } else { - o->buffersize = DEFAULT_RINGBUFFER_SIZE; - o->buffersize_given = 0; - } + for (;;) { + int ret; - if (pa_modargs_get_value(ma, "channel_map", NULL)) { - o->map_given = 1; - if (pa_modargs_get_channel_map(ma, &o->map) < 0) { - pa_log_error("Failed to parse the \"channel_map\" argument."); + if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) goto fail; - } - /* channel_map specifies the channel count too. */ - if (o->channels_given && (o->channels != o->map.channels)) { - pa_log_error( - "\"channels\" and \"channel_map\" arguments conficted. If you " - "use the \"channel_map\" argument, you can omit the " - "\"channels\" argument."); - goto fail; - } else { - o->channels = o->map.channels; - o->channels_given = 1; - } - } else { - /* The actual default value is the default alsa mappings, but that - can't be set until the channel count is known. Here we initialize - the map to some valid value, although the value won't be used. */ - pa_channel_map_init_stereo(&o->map); - o->map_given = 0; + if (ret == 0) + goto finish; } - pa_modargs_free(ma); - - return 0; - fail: - if (ma) - pa_modargs_free(ma); + /* If this was no regular exit from the loop we have to continue + * processing messages until we received PA_MESSAGE_SHUTDOWN */ + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); - return -1; +finish: + pa_log_debug("Thread shutting down"); } +static void jack_error_func(const char*t) { + char *s; -static void set_default_channels(pa_module* self, struct options* o) { - struct userdata* u; - const char **ports, **p; - - assert(self); - assert(o); - assert(self->userdata); - - u = self->userdata; - - assert(u->j_client); - assert(self->core); - - o->channels = 0; - - ports = jack_get_ports(u->j_client, NULL, JACK_DEFAULT_AUDIO_TYPE, - JackPortIsPhysical|JackPortIsInput); - - for (p = ports; *p; p++) - o->channels++; - - free(ports); - - if (o->channels >= PA_CHANNELS_MAX) - o->channels = PA_CHANNELS_MAX - 1; - - if (o->channels == 0) - o->channels = self->core->default_sample_spec.channels; + s = pa_xstrndup(t, strcspn(t, "\n\r")); + pa_log_warn("JACK error >%s<", s); + pa_xfree(s); } +static void jack_init(void *arg) { + struct userdata *u = arg; -static int create_sink(pa_module* self, struct options* o) { - struct userdata* u; - pa_sample_spec ss; - char *t; - - assert(self); - assert(o); - assert(self->userdata); - - u = self->userdata; - - assert(u->j_client); - - ss.channels = u->channels; - ss.rate = jack_get_sample_rate(u->j_client); - ss.format = PA_SAMPLE_FLOAT32NE; - assert(pa_sample_spec_valid(&ss)); + pa_log_info("JACK thread starting up."); - if (!(u->sink = pa_sink_new(self->core, __FILE__, o->sink_name, 0, &ss, - &o->map))) { - pa_log("failed to create sink."); - return -1; - } - - u->sink->userdata = u; - pa_sink_set_owner(u->sink, self); - - pa_sink_set_description( - u->sink, - t = pa_sprintf_malloc("Jack sink (%s)", - jack_get_client_name(u->j_client))); - pa_xfree(t); - - u->sink->get_latency = sink_get_latency_cb; - - return 0; + if (u->core->high_priority) + pa_make_realtime(); } +static void jack_shutdown(void* arg) { + struct userdata *u = arg; -static void connect_ports(pa_module* self) { - struct userdata* u; - unsigned i; - const char **ports, **p; - - assert(self); - assert(self->userdata); - - u = self->userdata; - - assert(u->j_client); - - ports = jack_get_ports(u->j_client, NULL, JACK_DEFAULT_AUDIO_TYPE, - JackPortIsPhysical|JackPortIsInput); - - for (i = 0, p = ports; i < u->channels; i++, p++) { - assert(u->j_ports[i]); - - if (!*p) { - pa_log("Not enough physical output ports, leaving unconnected."); - break; - } - - pa_log_info("connecting %s to %s", - jack_port_name(u->j_ports[i]), *p); - - if (jack_connect(u->j_client, jack_port_name(u->j_ports[i]), *p)) { - pa_log("Failed to connect %s to %s, leaving unconnected.", - jack_port_name(u->j_ports[i]), *p); - break; - } - } - - free(ports); + pa_log_info("JACK thread shutting down.."); + pa_asyncmsgq_post(u->jack_msgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_ON_SHUTDOWN, NULL, 0, NULL, NULL); } +int pa__init(pa_module*m) { + struct userdata *u = NULL; + pa_sample_spec ss; + pa_channel_map map; + pa_modargs *ma = NULL; + jack_status_t status; + const char *server_name, *client_name; + uint32_t channels = 0; + int do_connect = 1; + unsigned i; + const char **ports = NULL, **p; + char *t; -static int start_filling_ringbuffer(pa_module* self) { - struct userdata* u; - pthread_attr_t thread_attributes; + pa_assert(m); - assert(self); - assert(self->userdata); + jack_set_error_function(jack_error_func); - u = self->userdata; - - pthread_attr_init(&thread_attributes); - - if (pthread_attr_setinheritsched(&thread_attributes, - PTHREAD_INHERIT_SCHED) != 0) { - pa_log("pthread_attr_setinheritsched() failed."); + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments."); goto fail; } - else if (pthread_create(&u->filler_thread, &thread_attributes, - fill_ringbuffer, u) != 0) { - pa_log("pthread_create() failed."); + + if (pa_modargs_get_value_boolean(ma, "connect", &do_connect) < 0) { + pa_log("Failed to parse connect= argument."); goto fail; } - - u->filler_thread_is_running = 1; - - pthread_attr_destroy(&thread_attributes); - - return 0; - -fail: - pthread_attr_destroy(&thread_attributes); - return -1; -} + server_name = pa_modargs_get_value(ma, "server_name", NULL); + client_name = pa_modargs_get_value(ma, "client_name", "PulseAudio JACK Sink"); + + u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + m->userdata = u; + u->saved_frame_time_valid = FALSE; + pa_thread_mq_init(&u->thread_mq, m->core->mainloop); + u->rtpoll = pa_rtpoll_new(); + pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq); + + /* The queue linking the JACK thread and our RT thread */ + u->jack_msgq = pa_asyncmsgq_new(0); + + /* The msgq from the JACK RT thread should have an even higher + * priority than the normal message queues, to match the guarantee + * all other drivers make: supplying the audio device with data is + * the top priority -- and as long as that is possible we don't do + * anything else */ + u->rtpoll_item = pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY-1, u->jack_msgq); + + if (!(u->client = jack_client_open(client_name, server_name ? JackServerName : JackNullOption, &status, server_name))) { + pa_log("jack_client_open() failed."); + goto fail; + } -static void jack_error_func(const char* t) { - pa_log_warn("JACK error >%s<", t); -} - + ports = jack_get_ports(u->client, NULL, NULL, JackPortIsPhysical|JackPortIsInput); -static pa_usec_t sink_get_latency_cb(pa_sink* s) { - /* The latency is approximately the sum of the first port's latency, - buffersize of jack and the ringbuffer size. Maybe instead of using just - the first port, the max of all ports' latencies should be used? */ - struct userdata* u; - jack_nframes_t l; - - assert(s); - assert(s->userdata); - - u = s->userdata; - - l = jack_port_get_total_latency(u->j_client, u->j_ports[0]) + - u->j_buffersize + u->ringbuffer->size / u->frame_size; - - return pa_bytes_to_usec(l * u->frame_size, &s->sample_spec); -} + channels = 0; + for (p = ports; *p; p++) + channels++; + if (!channels) + channels = m->core->default_sample_spec.channels; -static int jack_process(jack_nframes_t nframes, void* arg) { - struct userdata* u = arg; - float* j_buffers[PA_CHANNELS_MAX]; - unsigned nsamples = u->channels * nframes; - unsigned sample_idx_part1, sample_idx_part2; - jack_nframes_t frame_idx; - jack_ringbuffer_data_t data[2]; /* In case the readable area in the - ringbuffer is non-continuous, the data - will be split in two parts. */ - unsigned chan; - unsigned samples_left_over; - - for (chan = 0; chan < u->channels; chan++) { - j_buffers[chan] = jack_port_get_buffer(u->j_ports[chan], nframes); - } - - jack_ringbuffer_get_read_vector(u->ringbuffer, data); - - /* We assume that the possible discontinuity doesn't happen in the middle - * of a sample. Should be a safe assumption. */ - assert(((data[0].len % sizeof(float)) == 0) || - (data[1].len == 0)); - - /* Copy from the first part of data until enough samples are copied or the - first part ends. */ - sample_idx_part1 = 0; - chan = 0; - frame_idx = 0; - while (sample_idx_part1 < nsamples && - ((sample_idx_part1 + 1) * sizeof(float)) <= data[0].len) { - float *s = ((float*) data[0].buf) + sample_idx_part1; - float *d = j_buffers[chan] + frame_idx; - *d = *s; - - sample_idx_part1++; - chan = (chan + 1) % u->channels; - frame_idx = sample_idx_part1 / u->channels; - } - - samples_left_over = nsamples - sample_idx_part1; - - /* Copy from the second part of data until enough samples are copied or the - second part ends. */ - sample_idx_part2 = 0; - while (sample_idx_part2 < samples_left_over && - ((sample_idx_part2 + 1) * sizeof(float)) <= data[1].len) { - float *s = ((float*) data[1].buf) + sample_idx_part2; - float *d = j_buffers[chan] + frame_idx; - *d = *s; - - sample_idx_part2++; - chan = (chan + 1) % u->channels; - frame_idx = (sample_idx_part1 + sample_idx_part2) / u->channels; + if (pa_modargs_get_value_u32(ma, "channels", &channels) < 0 || channels <= 0 || channels >= PA_CHANNELS_MAX) { + pa_log("Failed to parse channels= argument."); + goto fail; } - - samples_left_over -= sample_idx_part2; - - /* If there's still samples left, fill the buffers with zeros. */ - while (samples_left_over > 0) { - float *d = j_buffers[chan] + frame_idx; - *d = 0.0; - - samples_left_over--; - chan = (chan + 1) % u->channels; - frame_idx = (nsamples - samples_left_over) / u->channels; + + pa_channel_map_init_auto(&map, channels, PA_CHANNEL_MAP_ALSA); + if (pa_modargs_get_channel_map(ma, NULL, &map) < 0 || map.channels != channels) { + pa_log("Failed to parse channel_map= argument."); + goto fail; } - - jack_ringbuffer_read_advance( - u->ringbuffer, (sample_idx_part1 + sample_idx_part2) * sizeof(float)); - - /* Tell the rendering part that there is room in the ringbuffer. */ - u->ringbuffer_is_full = 0; - pthread_cond_signal(&u->ringbuffer_cond); - - return 0; -} + pa_log_info("Successfully connected as '%s'", jack_get_client_name(u->client)); + + ss.channels = u->channels = channels; + ss.rate = jack_get_sample_rate(u->client); + ss.format = PA_SAMPLE_FLOAT32NE; -static int jack_blocksize_cb(jack_nframes_t nframes, void* arg) { - /* This gets called in the processing thread, so do we have to be realtime - safe? No, we can do whatever we want. User gets silence while we do it. - - In addition to just updating the j_buffersize field in userdata, we have - to create a new ringbuffer, if the new buffer size is bigger or equal to - the old ringbuffer size. */ - struct userdata* u = arg; - - assert(u); - - /* We don't want to change the blocksize and the ringbuffer while rendering - is going on. */ - pthread_mutex_lock(&u->buffersize_mutex); - - u->j_buffersize = nframes; - - if ((u->ringbuffer->size / u->frame_size) <= nframes) { - /* We have to create a new ringbuffer. What are we going to do with the - old data in the old buffer? We throw it away, because we're lazy - coders. The listening experience is likely to get ruined anyway - during the blocksize change. */ - jack_ringbuffer_free(u->ringbuffer); - - /* The actual ringbuffer size will be rounded up to the nearest power - of two. */ - if (!(u->ringbuffer = - jack_ringbuffer_create((nframes + 1) * u->frame_size))) { - pa_log_error( - "jack_ringbuffer_create() failed while changing jack's buffer " - "size, module exiting."); - jack_client_close(u->j_client); - u->quit_requested = 1; + pa_assert(pa_sample_spec_valid(&ss)); + + for (i = 0; i < ss.channels; i++) { + if (!(u->port[i] = jack_port_register(u->client, pa_channel_position_to_string(map.map[i]), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput|JackPortIsTerminal, 0))) { + pa_log("jack_port_register() failed."); + goto fail; } - assert((u->ringbuffer->size % sizeof(float)) == 0); - pa_log_info("buffersize is %u frames (%u samples, %u bytes).", - u->ringbuffer->size / u->frame_size, - u->ringbuffer->size / sizeof(float), - u->ringbuffer->size); } - - pthread_mutex_unlock(&u->buffersize_mutex); - - return 0; -} + if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, &map))) { + pa_log("failed to create sink."); + goto fail; + } -static void jack_shutdown(void* arg) { - struct userdata* u = arg; - assert(u); + u->sink->parent.process_msg = sink_process_msg; + u->sink->userdata = u; + u->sink->flags = PA_SINK_LATENCY; - u->quit_requested = 1; - request_render(u); -} + pa_sink_set_module(u->sink, m); + pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); + pa_sink_set_rtpoll(u->sink, u->rtpoll); + pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Jack sink (%s)", jack_get_client_name(u->client))); + pa_xfree(t); + jack_set_process_callback(u->client, jack_process, u); + jack_on_shutdown(u->client, jack_shutdown, u); + jack_set_thread_init_callback(u->client, jack_init, u); -static void io_event_cb(pa_mainloop_api* m, pa_io_event* e, int fd, - pa_io_event_flags_t flags, void* userdata) { - pa_module* self = userdata; - struct userdata* u; - char x; - jack_ringbuffer_data_t buffer[2]; /* The write area in the ringbuffer may - be split in two parts. */ - pa_memchunk chunk; /* This is the data source. */ - unsigned part1_length, part2_length; - unsigned sample_idx_part1, sample_idx_part2; - unsigned chan; - unsigned frame_size; - int rem; - - assert(m); - assert(e); - assert(flags == PA_IO_EVENT_INPUT); - assert(self); - assert(self->userdata); - - u = self->userdata; - - assert(u->pipe_fds[0] == fd); - - pa_read(fd, &x, 1, &u->pipe_fd_type); - - if (u->quit_requested) { - pa_module_unload_request(self); - return; + if (!(u->thread = pa_thread_new(thread_func, u))) { + pa_log("Failed to create thread."); + goto fail; } - frame_size = u->frame_size; - - /* No blocksize changes during rendering, please. */ - pthread_mutex_lock(&u->buffersize_mutex); - - jack_ringbuffer_get_write_vector(u->ringbuffer, buffer); - assert(((buffer[0].len % sizeof(float)) == 0) || (buffer[1].len == 0)); - - part1_length = buffer[0].len / sizeof(float); - part2_length = buffer[1].len / sizeof(float); - - /* If the amount of free space is not a multiple of the frame size, we have - to truncate the lengths so that we process only complete frames. */ - if ((rem = (part1_length + part2_length) % u->channels) != 0) { - if (part2_length >= rem) { - part2_length -= rem; - } else { - part1_length -= rem - part2_length; - part2_length = 0; - } + if (jack_activate(u->client)) { + pa_log("jack_activate() failed"); + goto fail; } - - /* pa_sink_render_full doesn't accept zero length, so we have do the - copying only if there's data to copy, which actually makes a kind of - sense. */ - if (part1_length > 0 || part2_length > 0) { - pa_sink_render_full(u->sink, - (part1_length + part2_length) * sizeof(float), - &chunk); - - /* Write to the first part of the buffer. */ - for (sample_idx_part1 = 0; - sample_idx_part1 < part1_length; - sample_idx_part1++) { - float *s = - ((float*) ((uint8_t*) chunk.memblock->data + chunk.index)) + - sample_idx_part1; - float *d = ((float*) buffer[0].buf) + sample_idx_part1; - *d = *s; - } - - /* Write to the second part of the buffer. */ - for (sample_idx_part2 = 0; - sample_idx_part2 < part2_length; - sample_idx_part2++) { - float *s = - ((float*) ((uint8_t*) chunk.memblock->data + chunk.index)) + - sample_idx_part1 + sample_idx_part2; - float *d = ((float*) buffer[1].buf) + sample_idx_part2; - *d = *s; + + if (do_connect) { + for (i = 0, p = ports; i < ss.channels; i++, p++) { + + if (!*p) { + pa_log("Not enough physical output ports, leaving unconnected."); + break; + } + + pa_log_info("Connecting %s to %s", jack_port_name(u->port[i]), *p); + + if (jack_connect(u->client, jack_port_name(u->port[i]), *p)) { + pa_log("Failed to connect %s to %s, leaving unconnected.", jack_port_name(u->port[i]), *p); + break; + } } - - pa_memblock_unref(chunk.memblock); - - jack_ringbuffer_write_advance( - u->ringbuffer, (part1_length + part2_length) * sizeof(float)); } - - /* Blocksize can be changed again. */ - pthread_mutex_unlock(&u->buffersize_mutex); -} + pa_sink_put(u->sink); -static void* fill_ringbuffer(void* arg) { - struct userdata* u = arg; - - assert(u); - - while (!u->quit_requested) { - if (u->ringbuffer_is_full) { - pthread_mutex_lock(&u->cond_mutex); - pthread_cond_wait(&u->ringbuffer_cond, - &u->cond_mutex); - pthread_mutex_unlock(&u->cond_mutex); - } - /* No, it's not full yet, but this must be set to one as soon as - possible, because if the jack thread manages to process another - block before we set this to one, we may end up waiting without - a reason. */ - u->ringbuffer_is_full = 1; + free(ports); + pa_modargs_free(ma); - request_render(u); - } - - return NULL; -} + return 0; + +fail: + if (ma) + pa_modargs_free(ma); + + free(ports); + pa__done(m); -static void request_render(struct userdata* u) { - char c = 'x'; - - assert(u); - - assert(u->pipe_fds[1] >= 0); - pa_write(u->pipe_fds[1], &c, 1, &u->pipe_fd_type); + return -1; } -void pa__done(pa_core* c, pa_module* self) { - struct userdata* u; - - assert(c); - assert(self); +void pa__done(pa_module*m) { + struct userdata *u; + + pa_assert(m); - if (!self->userdata) + if (!(u = m->userdata)) return; - u = self->userdata; - - if (u->filler_thread_is_running) { - u->quit_requested = 1; - pthread_cond_signal(&u->ringbuffer_cond); - pthread_join(u->filler_thread, NULL); + if (u->client) + jack_client_close(u->client); + + if (u->sink) + pa_sink_unlink(u->sink); + + if (u->thread) { + pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); + pa_thread_free(u->thread); } - - if (u->j_client) - jack_client_close(u->j_client); - if (u->io_event) - c->mainloop->io_free(u->io_event); + pa_thread_mq_done(&u->thread_mq); - if (u->sink) { - pa_sink_disconnect(u->sink); + if (u->sink) pa_sink_unref(u->sink); - } - - if (u->ringbuffer) - jack_ringbuffer_free(u->ringbuffer); - - if (u->pipe_fds[0] >= 0) - pa_close(u->pipe_fds[0]); - if (u->pipe_fds[1] >= 0) - pa_close(u->pipe_fds[1]); - - pthread_mutex_destroy(&u->buffersize_mutex); - pthread_cond_destroy(&u->ringbuffer_cond); - pthread_mutex_destroy(&u->cond_mutex); - pa_xfree(self->userdata); - self->userdata = NULL; + + if (u->rtpoll_item) + pa_rtpoll_item_free(u->rtpoll_item); + + if (u->jack_msgq) + pa_asyncmsgq_unref(u->jack_msgq); + + if (u->rtpoll) + pa_rtpoll_free(u->rtpoll); + + pa_xfree(u); } diff --git a/src/modules/module-jack-source.c b/src/modules/module-jack-source.c index 8ca24035..b62ebe7a 100644 --- a/src/modules/module-jack-source.c +++ b/src/modules/module-jack-source.c @@ -28,31 +28,34 @@ #include <stdlib.h> #include <sys/stat.h> #include <stdio.h> -#include <assert.h> #include <errno.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <limits.h> -#include <pthread.h> #include <jack/jack.h> #include <pulse/xmalloc.h> #include <pulsecore/core-error.h> -#include <pulsecore/iochannel.h> #include <pulsecore/source.h> #include <pulsecore/module.h> #include <pulsecore/core-util.h> #include <pulsecore/modargs.h> #include <pulsecore/log.h> -#include <pulse/mainloop-api.h> +#include <pulsecore/thread.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/rtpoll.h> +#include <pulsecore/sample-util.h> #include "module-jack-source-symdef.h" +/* See module-jack-sink for a few comments how this module basically + * works */ + PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_DESCRIPTION("Jack Source") +PA_MODULE_DESCRIPTION("JACK Source") PA_MODULE_VERSION(PACKAGE_VERSION) PA_MODULE_USAGE( "source_name=<name of source> " @@ -67,7 +70,6 @@ PA_MODULE_USAGE( struct userdata { pa_core *core; pa_module *module; - pa_source *source; unsigned channels; @@ -75,19 +77,15 @@ struct userdata { jack_port_t* port[PA_CHANNELS_MAX]; jack_client_t *client; - pthread_mutex_t mutex; - pthread_cond_t cond; + pa_thread_mq thread_mq; + pa_asyncmsgq *jack_msgq; + pa_rtpoll *rtpoll; + pa_rtpoll_item *rtpoll_item; - void * buffer[PA_CHANNELS_MAX]; - jack_nframes_t frames_posted; - int quit_requested; + pa_thread *thread; - int pipe_fds[2]; - int pipe_fd_type; - pa_io_event *io_event; - - jack_nframes_t frames_in_buffer; - jack_nframes_t timestamp; + jack_nframes_t saved_frame_time; + pa_bool_t saved_frame_time_valid; }; static const char* const valid_modargs[] = { @@ -100,141 +98,150 @@ static const char* const valid_modargs[] = { NULL }; -static void stop_source(struct userdata *u) { - assert (u); - - jack_client_close(u->client); - u->client = NULL; - u->core->mainloop->io_free(u->io_event); - u->io_event = NULL; - pa_source_disconnect(u->source); - pa_source_unref(u->source); - u->source = NULL; - pa_module_unload_request(u->module); -} +enum { + SOURCE_MESSAGE_POST = PA_SOURCE_MESSAGE_MAX, + SOURCE_MESSAGE_ON_SHUTDOWN +}; -static void io_event_cb(pa_mainloop_api *m, pa_io_event *e, int fd, pa_io_event_flags_t flags, void *userdata) { - struct userdata *u = userdata; - char x; +static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SOURCE(o)->userdata; - assert(m); - assert(flags == PA_IO_EVENT_INPUT); - assert(u); - assert(u->pipe_fds[0] == fd); + switch (code) { - pa_read(fd, &x, 1, &u->pipe_fd_type); + case SOURCE_MESSAGE_POST: - if (u->quit_requested) { - stop_source(u); - u->quit_requested = 0; - return; - } + /* Handle the new block from the JACK thread */ + pa_assert(chunk); + pa_assert(chunk->length > 0); - pthread_mutex_lock(&u->mutex); + if (u->source->thread_info.state == PA_SOURCE_RUNNING) + pa_source_post(u->source, chunk); - if (u->frames_posted > 0) { - unsigned fs; - jack_nframes_t frame_idx; - pa_memchunk chunk; + u->saved_frame_time = offset; + u->saved_frame_time_valid = TRUE; - fs = pa_frame_size(&u->source->sample_spec); + return 0; - chunk.memblock = pa_memblock_new(u->core->mempool, chunk.length = u->frames_posted * fs); - chunk.index = 0; + case SOURCE_MESSAGE_ON_SHUTDOWN: + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + return 0; - for (frame_idx = 0; frame_idx < u->frames_posted; frame_idx ++) { - unsigned c; + case PA_SOURCE_MESSAGE_GET_LATENCY: { + jack_nframes_t l, ft, d; + size_t n; - for (c = 0; c < u->channels; c++) { - float *s = ((float*) u->buffer[c]) + frame_idx; - float *d = ((float*) ((uint8_t*) chunk.memblock->data + chunk.index)) + (frame_idx * u->channels) + c; + /* This is the "worst-case" latency */ + l = jack_port_get_total_latency(u->client, u->port[0]); - *d = *s; - } - } + if (u->saved_frame_time_valid) { + /* Adjust the worst case latency by the time that + * passed since we last handed data to JACK */ - pa_source_post(u->source, &chunk); - pa_memblock_unref(chunk.memblock); + ft = jack_frame_time(u->client); + d = ft > u->saved_frame_time ? ft - u->saved_frame_time : 0; + l += d; + } - u->frames_posted = 0; + /* Convert it to usec */ + n = l * pa_frame_size(&u->source->sample_spec); + *((pa_usec_t*) data) = pa_bytes_to_usec(n, &u->source->sample_spec); - pthread_cond_signal(&u->cond); + return 0; + } } - pthread_mutex_unlock(&u->mutex); -} - -static void request_post(struct userdata *u) { - char c = 'x'; - assert(u); - - assert(u->pipe_fds[1] >= 0); - pa_write(u->pipe_fds[1], &c, 1, &u->pipe_fd_type); -} - -static void jack_shutdown(void *arg) { - struct userdata *u = arg; - assert(u); - - u->quit_requested = 1; - request_post(u); + return pa_source_process_msg(o, code, data, offset, chunk); } static int jack_process(jack_nframes_t nframes, void *arg) { + unsigned c; struct userdata *u = arg; - assert(u); - - if (jack_transport_query(u->client, NULL) == JackTransportRolling) { - unsigned c; + const void *buffer[PA_CHANNELS_MAX]; + void *p; + jack_nframes_t frame_time; + pa_memchunk chunk; - pthread_mutex_lock(&u->mutex); + pa_assert(u); - u->frames_posted = nframes; + for (c = 0; c < u->channels; c++) + pa_assert(buffer[c] = jack_port_get_buffer(u->port[c], nframes)); - for (c = 0; c < u->channels; c++) { - u->buffer[c] = jack_port_get_buffer(u->port[c], nframes); - assert(u->buffer[c]); - } + /* We interleave the data and pass it on to the other RT thread */ - request_post(u); + pa_memchunk_reset(&chunk); + chunk.length = nframes * pa_frame_size(&u->source->sample_spec); + chunk.memblock = pa_memblock_new(u->core->mempool, chunk.length); + p = pa_memblock_acquire(chunk.memblock); + pa_interleave(buffer, u->channels, p, sizeof(float), nframes); + pa_memblock_release(chunk.memblock); - pthread_cond_wait(&u->cond, &u->mutex); + frame_time = jack_frame_time(u->client); - u->frames_in_buffer = nframes; - u->timestamp = jack_get_current_transport_frame(u->client); + pa_asyncmsgq_post(u->jack_msgq, PA_MSGOBJECT(u->source), SOURCE_MESSAGE_POST, NULL, frame_time, &chunk, NULL); - pthread_mutex_unlock(&u->mutex); - } + pa_memblock_unref(chunk.memblock); return 0; } -static pa_usec_t source_get_latency_cb(pa_source *s) { - struct userdata *u; - jack_nframes_t n, l, d; +static void thread_func(void *userdata) { + struct userdata *u = userdata; + + pa_assert(u); - assert(s); - u = s->userdata; + pa_log_debug("Thread starting up"); - if (jack_transport_query(u->client, NULL) != JackTransportRolling) - return 0; + if (u->core->high_priority) + pa_make_realtime(); - n = jack_get_current_transport_frame(u->client); + pa_thread_mq_install(&u->thread_mq); + pa_rtpoll_install(u->rtpoll); - if (n < u->timestamp) - return 0; + for (;;) { + int ret; - d = n - u->timestamp; - l = jack_port_get_total_latency(u->client, u->port[0]); + if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) + goto fail; + + if (ret == 0) + goto finish; + } - return pa_bytes_to_usec((l + d) * pa_frame_size(&s->sample_spec), &s->sample_spec); +fail: + /* If this was no regular exit from the loop we have to continue + * processing messages until we received PA_MESSAGE_SHUTDOWN */ + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + +finish: + pa_log_debug("Thread shutting down"); } static void jack_error_func(const char*t) { - pa_log_warn("JACK error >%s<", t); + char *s; + + s = pa_xstrndup(t, strcspn(t, "\n\r")); + pa_log_warn("JACK error >%s<", s); + pa_xfree(s); +} + +static void jack_init(void *arg) { + struct userdata *u = arg; + + pa_log_info("JACK thread starting up."); + + if (u->core->high_priority) + pa_make_realtime(); } -int pa__init(pa_core *c, pa_module*m) { +static void jack_shutdown(void* arg) { + struct userdata *u = arg; + + pa_log_info("JACK thread shutting down.."); + pa_asyncmsgq_post(u->jack_msgq, PA_MSGOBJECT(u->source), SOURCE_MESSAGE_ON_SHUTDOWN, NULL, 0, NULL, NULL); +} + +int pa__init(pa_module*m) { struct userdata *u = NULL; pa_sample_spec ss; pa_channel_map map; @@ -247,40 +254,35 @@ int pa__init(pa_core *c, pa_module*m) { const char **ports = NULL, **p; char *t; - assert(c); - assert(m); + pa_assert(m); jack_set_error_function(jack_error_func); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { - pa_log("failed to parse module arguments."); + pa_log("Failed to parse module arguments."); goto fail; } if (pa_modargs_get_value_boolean(ma, "connect", &do_connect) < 0) { - pa_log("failed to parse connect= argument."); + pa_log("Failed to parse connect= argument."); goto fail; } server_name = pa_modargs_get_value(ma, "server_name", NULL); - client_name = pa_modargs_get_value(ma, "client_name", "PulseAudio"); + client_name = pa_modargs_get_value(ma, "client_name", "PulseAudio JACK Source"); u = pa_xnew0(struct userdata, 1); - m->userdata = u; - u->core = c; + u->core = m->core; u->module = m; - u->pipe_fds[0] = u->pipe_fds[1] = -1; - u->pipe_fd_type = 0; - - pthread_mutex_init(&u->mutex, NULL); - pthread_cond_init(&u->cond, NULL); + m->userdata = u; + u->saved_frame_time_valid = FALSE; - if (pipe(u->pipe_fds) < 0) { - pa_log("pipe() failed: %s", pa_cstrerror(errno)); - goto fail; - } + pa_thread_mq_init(&u->thread_mq, m->core->mainloop); + u->rtpoll = pa_rtpoll_new(); + pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq); - pa_make_nonblock_fd(u->pipe_fds[1]); + u->jack_msgq = pa_asyncmsgq_new(0); + u->rtpoll_item = pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY-1, u->jack_msgq); if (!(u->client = jack_client_open(client_name, server_name ? JackServerName : JackNullOption, &status, server_name))) { pa_log("jack_client_open() failed."); @@ -294,7 +296,7 @@ int pa__init(pa_core *c, pa_module*m) { channels++; if (!channels) - channels = c->default_sample_spec.channels; + channels = m->core->default_sample_spec.channels; if (pa_modargs_get_value_u32(ma, "channels", &channels) < 0 || channels <= 0 || channels >= PA_CHANNELS_MAX) { pa_log("failed to parse channels= argument."); @@ -302,7 +304,7 @@ int pa__init(pa_core *c, pa_module*m) { } pa_channel_map_init_auto(&map, channels, PA_CHANNEL_MAP_ALSA); - if (pa_modargs_get_channel_map(ma, &map) < 0 || map.channels != channels) { + if (pa_modargs_get_channel_map(ma, NULL, &map) < 0 || map.channels != channels) { pa_log("failed to parse channel_map= argument."); goto fail; } @@ -313,7 +315,7 @@ int pa__init(pa_core *c, pa_module*m) { ss.rate = jack_get_sample_rate(u->client); ss.format = PA_SAMPLE_FLOAT32NE; - assert(pa_sample_spec_valid(&ss)); + pa_assert(pa_sample_spec_valid(&ss)); for (i = 0; i < ss.channels; i++) { if (!(u->port[i] = jack_port_register(u->client, pa_channel_position_to_string(map.map[i]), JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput|JackPortIsTerminal, 0))) { @@ -322,19 +324,29 @@ int pa__init(pa_core *c, pa_module*m) { } } - if (!(u->source = pa_source_new(c, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, &map))) { + if (!(u->source = pa_source_new(m->core, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, &map))) { pa_log("failed to create source."); goto fail; } + u->source->parent.process_msg = source_process_msg; u->source->userdata = u; - pa_source_set_owner(u->source, m); + u->source->flags = PA_SOURCE_LATENCY; + + pa_source_set_module(u->source, m); + pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); + pa_source_set_rtpoll(u->source, u->rtpoll); pa_source_set_description(u->source, t = pa_sprintf_malloc("Jack source (%s)", jack_get_client_name(u->client))); pa_xfree(t); - u->source->get_latency = source_get_latency_cb; jack_set_process_callback(u->client, jack_process, u); jack_on_shutdown(u->client, jack_shutdown, u); + jack_set_thread_init_callback(u->client, jack_init, u); + + if (!(u->thread = pa_thread_new(thread_func, u))) { + pa_log("Failed to create thread."); + goto fail; + } if (jack_activate(u->client)) { pa_log("jack_activate() failed"); @@ -359,7 +371,7 @@ int pa__init(pa_core *c, pa_module*m) { } - u->io_event = c->mainloop->io_new(c->mainloop, u->pipe_fds[0], PA_IO_EVENT_INPUT, io_event_cb, u); + pa_source_put(u->source); free(ports); pa_modargs_free(ma); @@ -372,14 +384,14 @@ fail: free(ports); - pa__done(c, m); + pa__done(m); return -1; } -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module*m) { struct userdata *u; - assert(c && m); + pa_assert(m); if (!(u = m->userdata)) return; @@ -387,20 +399,27 @@ void pa__done(pa_core *c, pa_module*m) { if (u->client) jack_client_close(u->client); - if (u->io_event) - c->mainloop->io_free(u->io_event); + if (u->source) + pa_source_unlink(u->source); - if (u->source) { - pa_source_disconnect(u->source); - pa_source_unref(u->source); + if (u->thread) { + pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); + pa_thread_free(u->thread); } - if (u->pipe_fds[0] >= 0) - close(u->pipe_fds[0]); - if (u->pipe_fds[1] >= 0) - close(u->pipe_fds[1]); + pa_thread_mq_done(&u->thread_mq); + + if (u->source) + pa_source_unref(u->source); + + if (u->rtpoll_item) + pa_rtpoll_item_free(u->rtpoll_item); + + if (u->jack_msgq) + pa_asyncmsgq_unref(u->jack_msgq); + + if (u->rtpoll) + pa_rtpoll_free(u->rtpoll); - pthread_mutex_destroy(&u->mutex); - pthread_cond_destroy(&u->cond); pa_xfree(u); } diff --git a/src/modules/module-ladspa-sink.c b/src/modules/module-ladspa-sink.c new file mode 100644 index 00000000..0265d971 --- /dev/null +++ b/src/modules/module-ladspa-sink.c @@ -0,0 +1,673 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/* TODO: Some plugins cause latency, and some even report it by using a control + out port. We don't currently use the latency information. */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/namereg.h> +#include <pulsecore/sink.h> +#include <pulsecore/module.h> +#include <pulsecore/core-util.h> +#include <pulsecore/modargs.h> +#include <pulsecore/log.h> +#include <pulsecore/thread.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/rtpoll.h> +#include <pulsecore/sample-util.h> + +#include "module-ladspa-sink-symdef.h" +#include "ladspa.h" + +PA_MODULE_AUTHOR("Lennart Poettering") +PA_MODULE_DESCRIPTION("Virtual LADSPA sink") +PA_MODULE_VERSION(PACKAGE_VERSION) +PA_MODULE_USAGE( + "sink_name=<name for the sink> " + "master=<name of sink to remap> " + "format=<sample format> " + "channels=<number of channels> " + "rate=<sample rate> " + "channel_map=<channel map> " + "plugin=<ladspa plugin name> " + "label=<ladspa plugin label> " + "control=<comma seperated list of input control values>") + +struct userdata { + pa_core *core; + pa_module *module; + + pa_sink *sink, *master; + pa_sink_input *sink_input; + + const LADSPA_Descriptor *descriptor; + unsigned channels; + LADSPA_Handle handle[PA_CHANNELS_MAX]; + LADSPA_Data *input, *output; + size_t block_size; + unsigned long input_port, output_port; + LADSPA_Data *control; + + /* This is a dummy buffer. Every port must be connected, but we don't care + about control out ports. We connect them all to this single buffer. */ + LADSPA_Data control_out; + + pa_memchunk memchunk; +}; + +static const char* const valid_modargs[] = { + "sink_name", + "master", + "format", + "channels", + "rate", + "channel_map", + "plugin", + "label", + "control", + NULL +}; + +/* Called from I/O thread context */ +static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SINK(o)->userdata; + + switch (code) { + + case PA_SINK_MESSAGE_GET_LATENCY: { + pa_usec_t usec = 0; + + if (PA_MSGOBJECT(u->master)->process_msg(PA_MSGOBJECT(u->master), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0) + usec = 0; + + *((pa_usec_t*) data) = usec + pa_bytes_to_usec(u->memchunk.length, &u->sink->sample_spec); + return 0; + } + } + + return pa_sink_process_msg(o, code, data, offset, chunk); +} + +/* Called from main context */ +static int sink_set_state(pa_sink *s, pa_sink_state_t state) { + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (PA_SINK_LINKED(state) && u->sink_input && PA_SINK_INPUT_LINKED(pa_sink_input_get_state(u->sink_input))) + pa_sink_input_cork(u->sink_input, state == PA_SINK_SUSPENDED); + + return 0; +} + +/* Called from I/O thread context */ +static int sink_input_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SINK_INPUT(o)->userdata; + + switch (code) { + case PA_SINK_INPUT_MESSAGE_GET_LATENCY: + *((pa_usec_t*) data) = pa_bytes_to_usec(u->memchunk.length, &u->sink_input->sample_spec); + + /* Fall through, the default handler will add in the extra + * latency added by the resampler */ + break; + } + + return pa_sink_input_process_msg(o, code, data, offset, chunk); +} + +/* Called from I/O thread context */ +static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + if (!u->memchunk.memblock) { + pa_memchunk tchunk; + float *src, *dst; + size_t fs; + unsigned n, c; + + pa_sink_render(u->sink, length, &tchunk); + + fs = pa_frame_size(&i->sample_spec); + n = tchunk.length / fs; + + pa_assert(n > 0); + + u->memchunk.memblock = pa_memblock_new(i->sink->core->mempool, tchunk.length); + u->memchunk.index = 0; + u->memchunk.length = tchunk.length; + + src = (float*) ((uint8_t*) pa_memblock_acquire(tchunk.memblock) + tchunk.index); + dst = (float*) pa_memblock_acquire(u->memchunk.memblock); + + for (c = 0; c < u->channels; c++) { + unsigned j; + float *p, *q; + + p = src + c; + q = u->input; + for (j = 0; j < n; j++, p += u->channels, q++) + *q = CLAMP(*p, -1.0, 1.0); + + u->descriptor->run(u->handle[c], n); + + q = u->output; + p = dst + c; + for (j = 0; j < n; j++, q++, p += u->channels) + *p = CLAMP(*q, -1.0, 1.0); + } + + pa_memblock_release(tchunk.memblock); + pa_memblock_release(u->memchunk.memblock); + + pa_memblock_unref(tchunk.memblock); + } + + pa_assert(u->memchunk.length > 0); + pa_assert(u->memchunk.memblock); + + *chunk = u->memchunk; + pa_memblock_ref(chunk->memblock); + + return 0; +} + +/* Called from I/O thread context */ +static void sink_input_drop_cb(pa_sink_input *i, size_t length) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + pa_assert(length > 0); + + if (u->memchunk.memblock) { + + if (length < u->memchunk.length) { + u->memchunk.index += length; + u->memchunk.length -= length; + return; + } + + pa_memblock_unref(u->memchunk.memblock); + length -= u->memchunk.length; + pa_memchunk_reset(&u->memchunk); + } + + if (length > 0) + pa_sink_skip(u->sink, length); +} + +/* Called from I/O thread context */ +static void sink_input_detach_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_sink_detach_within_thread(u->sink); +} + +/* Called from I/O thread context */ +static void sink_input_attach_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_sink_set_asyncmsgq(u->sink, i->sink->asyncmsgq); + pa_sink_set_rtpoll(u->sink, i->sink->rtpoll); + + pa_sink_attach_within_thread(u->sink); +} + +/* Called from main context */ +static void sink_input_kill_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_sink_input_unlink(u->sink_input); + pa_sink_input_unref(u->sink_input); + u->sink_input = NULL; + + pa_sink_unlink(u->sink); + pa_sink_unref(u->sink); + u->sink = NULL; + + pa_module_unload_request(u->module); +} + +int pa__init(pa_module*m) { + struct userdata *u; + pa_sample_spec ss; + pa_channel_map map; + pa_modargs *ma; + char *t; + pa_sink *master; + pa_sink_input_new_data data; + const char *plugin, *label; + LADSPA_Descriptor_Function descriptor_func; + const char *e, *cdata; + const LADSPA_Descriptor *d; + unsigned long input_port, output_port, p, j, n_control; + unsigned c; + pa_bool_t *use_default = NULL; + char *default_sink_name = NULL; + + pa_assert(m); + + pa_assert(sizeof(LADSPA_Data) == sizeof(float)); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments."); + goto fail; + } + + if (!(master = pa_namereg_get(m->core, pa_modargs_get_value(ma, "master", NULL), PA_NAMEREG_SINK, 1))) { + pa_log("Master sink not found"); + goto fail; + } + + ss = master->sample_spec; + ss.format = PA_SAMPLE_FLOAT32; + map = master->channel_map; + if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) { + pa_log("Invalid sample format specification or channel map"); + goto fail; + } + + if (!(plugin = pa_modargs_get_value(ma, "plugin", NULL))) { + pa_log("Missing LADSPA plugin name"); + goto fail; + } + + if (!(label = pa_modargs_get_value(ma, "label", NULL))) { + pa_log("Missing LADSPA plugin label"); + goto fail; + } + + cdata = pa_modargs_get_value(ma, "control", NULL); + + u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + m->userdata = u; + u->master = master; + pa_memchunk_reset(&u->memchunk); + + if (!(e = getenv("LADSPA_PATH"))) + e = LADSPA_PATH; + + /* FIXME: This is not exactly thread safe */ + t = pa_xstrdup(lt_dlgetsearchpath()); + lt_dlsetsearchpath(e); + m->dl = lt_dlopenext(plugin); + lt_dlsetsearchpath(t); + pa_xfree(t); + + if (!m->dl) { + pa_log("Failed to load LADSPA plugin: %s", lt_dlerror()); + goto fail; + } + + if (!(descriptor_func = (LADSPA_Descriptor_Function) lt_dlsym(m->dl, "ladspa_descriptor"))) { + pa_log("LADSPA module lacks ladspa_descriptor() symbol."); + goto fail; + } + + for (j = 0;; j++) { + + if (!(d = descriptor_func(j))) { + pa_log("Failed to find plugin label '%s' in plugin '%s'.", plugin, label); + goto fail; + } + + if (strcmp(d->Label, label) == 0) + break; + } + + u->descriptor = d; + + pa_log_debug("Module: %s", plugin); + pa_log_debug("Label: %s", d->Label); + pa_log_debug("Unique ID: %lu", d->UniqueID); + pa_log_debug("Name: %s", d->Name); + pa_log_debug("Maker: %s", d->Maker); + pa_log_debug("Copyright: %s", d->Copyright); + + input_port = output_port = (unsigned long) -1; + n_control = 0; + + for (p = 0; p < d->PortCount; p++) { + + if (LADSPA_IS_PORT_INPUT(d->PortDescriptors[p]) && LADSPA_IS_PORT_AUDIO(d->PortDescriptors[p])) { + + if (strcmp(d->PortNames[p], "Input") == 0) { + pa_assert(input_port == (unsigned long) -1); + input_port = p; + } else { + pa_log("Found audio input port on plugin we cannot handle: %s", d->PortNames[p]); + goto fail; + } + + } else if (LADSPA_IS_PORT_OUTPUT(d->PortDescriptors[p]) && LADSPA_IS_PORT_AUDIO(d->PortDescriptors[p])) { + + if (strcmp(d->PortNames[p], "Output") == 0) { + pa_assert(output_port == (unsigned long) -1); + output_port = p; + } else { + pa_log("Found audio output port on plugin we cannot handle: %s", d->PortNames[p]); + goto fail; + } + + } else if (LADSPA_IS_PORT_INPUT(d->PortDescriptors[p]) && LADSPA_IS_PORT_CONTROL(d->PortDescriptors[p])) + n_control++; + else { + pa_assert(LADSPA_IS_PORT_OUTPUT(d->PortDescriptors[p]) && LADSPA_IS_PORT_CONTROL(d->PortDescriptors[p])); + pa_log_info("Ignored port \"%s\", because we ignore all control out ports.", d->PortNames[p]); + } + } + + if ((input_port == (unsigned long) -1) || (output_port == (unsigned long) -1)) { + pa_log("Failed to identify input and output ports. " + "Right now this module can only deal with plugins which provide an 'Input' and an 'Output' audio port. " + "Patches welcome!"); + goto fail; + } + + u->block_size = pa_frame_align(pa_mempool_block_size_max(m->core->mempool), &ss); + + u->input = (LADSPA_Data*) pa_xnew(uint8_t, u->block_size); + if (LADSPA_IS_INPLACE_BROKEN(d->Properties)) + u->output = (LADSPA_Data*) pa_xnew(uint8_t, u->block_size); + else + u->output = u->input; + + u->channels = ss.channels; + + for (c = 0; c < ss.channels; c++) { + if (!(u->handle[c] = d->instantiate(d, ss.rate))) { + pa_log("Failed to instantiate plugin %s with label %s for channel %i", plugin, d->Label, c); + goto fail; + } + + d->connect_port(u->handle[c], input_port, u->input); + d->connect_port(u->handle[c], output_port, u->output); + } + + if (!cdata && n_control > 0) { + pa_log("This plugin requires specification of %lu control parameters.", n_control); + goto fail; + } + + if (n_control > 0) { + const char *state = NULL; + char *k; + unsigned long h; + + u->control = pa_xnew(LADSPA_Data, n_control); + use_default = pa_xnew(pa_bool_t, n_control); + p = 0; + + while ((k = pa_split(cdata, ",", &state))) { + float f; + + if (*k == 0) { + use_default[p++] = TRUE; + pa_xfree(k); + continue; + } + + if (pa_atof(k, &f) < 0) { + pa_log("Failed to parse control value '%s'", k); + pa_xfree(k); + goto fail; + } + + pa_xfree(k); + + if (p >= n_control) { + pa_log("Too many control values passed, %lu expected.", n_control); + goto fail; + } + + use_default[p] = FALSE; + u->control[p++] = f; + } + + if (p < n_control) { + pa_log("Not enough control values passed, %lu expected, %lu passed.", n_control, p); + goto fail; + } + + h = 0; + for (p = 0; p < d->PortCount; p++) { + LADSPA_PortRangeHintDescriptor hint = d->PortRangeHints[p].HintDescriptor; + + if (!LADSPA_IS_PORT_CONTROL(d->PortDescriptors[p])) + continue; + + if (LADSPA_IS_PORT_OUTPUT(d->PortDescriptors[p])) { + for (c = 0; c < ss.channels; c++) + d->connect_port(u->handle[c], p, &u->control_out); + continue; + } + + pa_assert(h < n_control); + + if (use_default[h]) { + LADSPA_Data lower, upper; + + if (!LADSPA_IS_HINT_HAS_DEFAULT(hint)) { + pa_log("Control port value left empty but plugin defines no default."); + goto fail; + } + + lower = d->PortRangeHints[p].LowerBound; + upper = d->PortRangeHints[p].UpperBound; + + if (LADSPA_IS_HINT_SAMPLE_RATE(hint)) { + lower *= ss.rate; + upper *= ss.rate; + } + + switch (hint & LADSPA_HINT_DEFAULT_MASK) { + + case LADSPA_HINT_DEFAULT_MINIMUM: + u->control[h] = lower; + break; + + case LADSPA_HINT_DEFAULT_MAXIMUM: + u->control[h] = upper; + break; + + case LADSPA_HINT_DEFAULT_LOW: + if (LADSPA_IS_HINT_LOGARITHMIC(hint)) + u->control[h] = exp(log(lower) * 0.75 + log(upper) * 0.25); + else + u->control[h] = lower * 0.75 + upper * 0.25; + break; + + case LADSPA_HINT_DEFAULT_MIDDLE: + if (LADSPA_IS_HINT_LOGARITHMIC(hint)) + u->control[h] = exp(log(lower) * 0.5 + log(upper) * 0.5); + else + u->control[h] = lower * 0.5 + upper * 0.5; + break; + + case LADSPA_HINT_DEFAULT_HIGH: + if (LADSPA_IS_HINT_LOGARITHMIC(hint)) + u->control[h] = exp(log(lower) * 0.25 + log(upper) * 0.75); + else + u->control[h] = lower * 0.25 + upper * 0.75; + break; + + case LADSPA_HINT_DEFAULT_0: + u->control[h] = 0; + break; + + case LADSPA_HINT_DEFAULT_1: + u->control[h] = 1; + break; + + case LADSPA_HINT_DEFAULT_100: + u->control[h] = 100; + break; + + case LADSPA_HINT_DEFAULT_440: + u->control[h] = 440; + break; + + default: + pa_assert_not_reached(); + } + } + + if (LADSPA_IS_HINT_INTEGER(hint)) + u->control[h] = roundf(u->control[h]); + + pa_log_debug("Binding %f to port %s", u->control[h], d->PortNames[p]); + + for (c = 0; c < ss.channels; c++) + d->connect_port(u->handle[c], p, &u->control[h]); + + h++; + } + + pa_assert(h == n_control); + } + + if (d->activate) + for (c = 0; c < u->channels; c++) + d->activate(u->handle[c]); + + default_sink_name = pa_sprintf_malloc("%s.ladspa", master->name); + + /* Create sink */ + if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", default_sink_name), 0, &ss, &map))) { + pa_log("Failed to create sink."); + goto fail; + } + + u->sink->parent.process_msg = sink_process_msg; + u->sink->set_state = sink_set_state; + u->sink->userdata = u; + u->sink->flags = PA_SINK_LATENCY; + + pa_sink_set_module(u->sink, m); + pa_sink_set_description(u->sink, t = pa_sprintf_malloc("LADSPA plugin '%s' on '%s'", label, master->description)); + pa_xfree(t); + pa_sink_set_asyncmsgq(u->sink, master->asyncmsgq); + pa_sink_set_rtpoll(u->sink, master->rtpoll); + + /* Create sink input */ + pa_sink_input_new_data_init(&data); + data.sink = u->master; + data.driver = __FILE__; + data.name = "LADSPA Stream"; + pa_sink_input_new_data_set_sample_spec(&data, &ss); + pa_sink_input_new_data_set_channel_map(&data, &map); + data.module = m; + + if (!(u->sink_input = pa_sink_input_new(m->core, &data, PA_SINK_INPUT_DONT_MOVE))) + goto fail; + + u->sink_input->parent.process_msg = sink_input_process_msg; + u->sink_input->peek = sink_input_peek_cb; + u->sink_input->drop = sink_input_drop_cb; + u->sink_input->kill = sink_input_kill_cb; + u->sink_input->attach = sink_input_attach_cb; + u->sink_input->detach = sink_input_detach_cb; + u->sink_input->userdata = u; + + pa_sink_put(u->sink); + pa_sink_input_put(u->sink_input); + + pa_modargs_free(ma); + + pa_xfree(use_default); + pa_xfree(default_sink_name); + + return 0; + +fail: + if (ma) + pa_modargs_free(ma); + + pa_xfree(use_default); + pa_xfree(default_sink_name); + + pa__done(m); + + return -1; +} + +void pa__done(pa_module*m) { + struct userdata *u; + unsigned c; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->sink_input) { + pa_sink_input_unlink(u->sink_input); + pa_sink_input_unref(u->sink_input); + } + + if (u->sink) { + pa_sink_unlink(u->sink); + pa_sink_unref(u->sink); + } + + if (u->memchunk.memblock) + pa_memblock_unref(u->memchunk.memblock); + + for (c = 0; c < u->channels; c++) + if (u->handle[c]) { + if (u->descriptor->deactivate) + u->descriptor->deactivate(u->handle[c]); + u->descriptor->cleanup(u->handle[c]); + } + + if (u->output != u->input) + pa_xfree(u->output); + + pa_xfree(u->input); + + pa_xfree(u->control); + + pa_xfree(u); +} diff --git a/src/modules/module-lirc.c b/src/modules/module-lirc.c index c8adbc8b..21d93837 100644 --- a/src/modules/module-lirc.c +++ b/src/modules/module-lirc.c @@ -26,12 +26,12 @@ #endif #include <stdio.h> -#include <assert.h> #include <unistd.h> #include <string.h> -#include <lirc/lirc_client.h> #include <stdlib.h> +#include <lirc/lirc_client.h> + #include <pulse/xmalloc.h> #include <pulsecore/module.h> @@ -39,6 +39,7 @@ #include <pulsecore/namereg.h> #include <pulsecore/sink.h> #include <pulsecore/modargs.h> +#include <pulsecore/macro.h> #include "module-lirc-symdef.h" @@ -68,11 +69,12 @@ static int lirc_in_use = 0; static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GCC_UNUSED int fd, pa_io_event_flags_t events, void*userdata) { struct userdata *u = userdata; char *name = NULL, *code = NULL; - assert(io); - assert(u); + + pa_assert(io); + pa_assert(u); if (events & (PA_IO_EVENT_HANGUP|PA_IO_EVENT_ERROR)) { - pa_log("lost connection to LIRC daemon."); + pa_log("Lost connection to LIRC daemon."); goto fail; } @@ -86,7 +88,7 @@ static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GC c = pa_xstrdup(code); c[strcspn(c, "\n\r")] = 0; - pa_log_debug("raw IR code '%s'", c); + pa_log_debug("Raw IR code '%s'", c); pa_xfree(c); while (lirc_code2char(u->config, code, &name) == 0 && name) { @@ -99,7 +101,7 @@ static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GC MUTE_TOGGLE } volchange = INVALID; - pa_log_info("translated IR code '%s'", name); + pa_log_info("Translated IR code '%s'", name); if (strcasecmp(name, "volume-up") == 0) volchange = UP; @@ -113,15 +115,15 @@ static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GC volchange = RESET; if (volchange == INVALID) - pa_log_warn("recieved unknown IR code '%s'", name); + pa_log_warn("Recieved unknown IR code '%s'", name); else { pa_sink *s; if (!(s = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK, 1))) - pa_log("failed to get sink '%s'", u->sink_name); + pa_log("Failed to get sink '%s'", u->sink_name); else { int i; - pa_cvolume cv = *pa_sink_get_volume(s, PA_MIXER_HARDWARE); + pa_cvolume cv = *pa_sink_get_volume(s); #define DELTA (PA_VOLUME_NORM/20) @@ -134,7 +136,7 @@ static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GC cv.values[i] = PA_VOLUME_NORM; } - pa_sink_set_volume(s, PA_MIXER_HARDWARE, &cv); + pa_sink_set_volume(s, &cv); break; case DOWN: @@ -145,20 +147,20 @@ static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GC cv.values[i] = PA_VOLUME_MUTED; } - pa_sink_set_volume(s, PA_MIXER_HARDWARE, &cv); + pa_sink_set_volume(s, &cv); break; case MUTE: - pa_sink_set_mute(s, PA_MIXER_HARDWARE, 0); + pa_sink_set_mute(s, 0); break; case RESET: - pa_sink_set_mute(s, PA_MIXER_HARDWARE, 1); + pa_sink_set_mute(s, 1); break; case MUTE_TOGGLE: - pa_sink_set_mute(s, PA_MIXER_HARDWARE, !pa_sink_get_mute(s, PA_MIXER_HARDWARE)); + pa_sink_set_mute(s, !pa_sink_get_mute(s)); break; case INVALID: @@ -179,13 +181,14 @@ fail: pa_module_unload_request(u->module); - free(code); + pa_xfree(code); } -int pa__init(pa_core *c, pa_module*m) { +int pa__init(pa_module*m) { pa_modargs *ma = NULL; struct userdata *u; - assert(c && m); + + pa_assert(m); if (lirc_in_use) { pa_log("module-lirc may no be loaded twice."); @@ -197,7 +200,7 @@ int pa__init(pa_core *c, pa_module*m) { goto fail; } - m->userdata = u = pa_xmalloc(sizeof(struct userdata)); + m->userdata = u = pa_xnew(struct userdata, 1); u->module = m; u->io = NULL; u->config = NULL; @@ -215,7 +218,7 @@ int pa__init(pa_core *c, pa_module*m) { goto fail; } - u->io = c->mainloop->io_new(c->mainloop, u->lirc_fd, PA_IO_EVENT_INPUT|PA_IO_EVENT_HANGUP, io_callback, u); + u->io = m->core->mainloop->io_new(m->core->mainloop, u->lirc_fd, PA_IO_EVENT_INPUT|PA_IO_EVENT_HANGUP, io_callback, u); lirc_in_use = 1; @@ -228,14 +231,13 @@ fail: if (ma) pa_modargs_free(ma); - pa__done(c, m); + pa__done(m); return -1; } -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module*m) { struct userdata *u; - assert(c); - assert(m); + pa_assert(m); if (!(u = m->userdata)) return; diff --git a/src/modules/module-match.c b/src/modules/module-match.c index 0b051fac..0155b2af 100644 --- a/src/modules/module-match.c +++ b/src/modules/module-match.c @@ -26,7 +26,6 @@ #endif #include <unistd.h> -#include <assert.h> #include <string.h> #include <errno.h> #include <sys/types.h> @@ -80,6 +79,8 @@ static int load_rules(struct userdata *u, const char *filename) { struct rule *end = NULL; char *fn = NULL; + pa_assert(u); + f = filename ? fopen(fn = pa_xstrdup(filename), "r") : pa_open_config_file(DEFAULT_MATCH_TABLE_FILE, DEFAULT_MATCH_TABLE_FILE_USER, NULL, &fn, "r"); @@ -132,7 +133,7 @@ static int load_rules(struct userdata *u, const char *filename) { goto finish; } - rule = pa_xmalloc(sizeof(struct rule)); + rule = pa_xnew(struct rule, 1); rule->regex = regex; rule->volume = volume; rule->next = NULL; @@ -164,7 +165,9 @@ static void callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, v struct userdata *u = userdata; pa_sink_input *si; struct rule *r; - assert(c && u); + + pa_assert(c); + pa_assert(u); if (t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW)) return; @@ -179,23 +182,24 @@ static void callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, v if (!regexec(&r->regex, si->name, 0, NULL, 0)) { pa_cvolume cv; pa_log_debug("changing volume of sink input '%s' to 0x%03x", si->name, r->volume); - pa_cvolume_set(&cv, r->volume, si->sample_spec.channels); + pa_cvolume_set(&cv, si->sample_spec.channels, r->volume); pa_sink_input_set_volume(si, &cv); } } } -int pa__init(pa_core *c, pa_module*m) { +int pa__init(pa_module*m) { pa_modargs *ma = NULL; struct userdata *u; - assert(c && m); + + pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { pa_log("Failed to parse module arguments"); goto fail; } - u = pa_xmalloc(sizeof(struct userdata)); + u = pa_xnew(struct userdata, 1); u->rules = NULL; u->subscription = NULL; m->userdata = u; @@ -203,23 +207,24 @@ int pa__init(pa_core *c, pa_module*m) { if (load_rules(u, pa_modargs_get_value(ma, "table", NULL)) < 0) goto fail; - u->subscription = pa_subscription_new(c, PA_SUBSCRIPTION_MASK_SINK_INPUT, callback, u); + u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_SINK_INPUT, callback, u); pa_modargs_free(ma); return 0; fail: - pa__done(c, m); + pa__done(m); if (ma) pa_modargs_free(ma); return -1; } -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module*m) { struct userdata* u; struct rule *r, *n; - assert(c && m); + + pa_assert(m); if (!(u = m->userdata)) return; diff --git a/src/modules/module-mmkbd-evdev.c b/src/modules/module-mmkbd-evdev.c index b7433ac8..47a10645 100644 --- a/src/modules/module-mmkbd-evdev.c +++ b/src/modules/module-mmkbd-evdev.c @@ -26,7 +26,6 @@ #endif #include <stdio.h> -#include <assert.h> #include <unistd.h> #include <string.h> #include <stdlib.h> @@ -80,11 +79,12 @@ struct userdata { static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GCC_UNUSED int fd, pa_io_event_flags_t events, void*userdata) { struct userdata *u = userdata; - assert(io); - assert(u); + + pa_assert(io); + pa_assert(u); if (events & (PA_IO_EVENT_HANGUP|PA_IO_EVENT_ERROR)) { - pa_log("lost connection to evdev device."); + pa_log("Lost connection to evdev device."); goto fail; } @@ -92,14 +92,14 @@ static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GC struct input_event ev; if (pa_loop_read(u->fd, &ev, sizeof(ev), &u->fd_type) <= 0) { - pa_log("failed to read from event device: %s", pa_cstrerror(errno)); + pa_log("Failed to read from event device: %s", pa_cstrerror(errno)); goto fail; } if (ev.type == EV_KEY && (ev.value == 1 || ev.value == 2)) { enum { INVALID, UP, DOWN, MUTE_TOGGLE } volchange = INVALID; - pa_log_debug("key code=%u, value=%u", ev.code, ev.value); + pa_log_debug("Key code=%u, value=%u", ev.code, ev.value); switch (ev.code) { case KEY_VOLUMEDOWN: volchange = DOWN; break; @@ -111,10 +111,10 @@ static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GC pa_sink *s; if (!(s = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK, 1))) - pa_log("failed to get sink '%s'", u->sink_name); + pa_log("Failed to get sink '%s'", u->sink_name); else { int i; - pa_cvolume cv = *pa_sink_get_volume(s, PA_MIXER_HARDWARE); + pa_cvolume cv = *pa_sink_get_volume(s); #define DELTA (PA_VOLUME_NORM/20) @@ -127,7 +127,7 @@ static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GC cv.values[i] = PA_VOLUME_NORM; } - pa_sink_set_volume(s, PA_MIXER_HARDWARE, &cv); + pa_sink_set_volume(s, &cv); break; case DOWN: @@ -138,12 +138,12 @@ static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GC cv.values[i] = PA_VOLUME_MUTED; } - pa_sink_set_volume(s, PA_MIXER_HARDWARE, &cv); + pa_sink_set_volume(s, &cv); break; case MUTE_TOGGLE: - pa_sink_set_mute(s, PA_MIXER_HARDWARE, !pa_sink_get_mute(s, PA_MIXER_HARDWARE)); + pa_sink_set_mute(s, !pa_sink_get_mute(s)); break; case INVALID: @@ -165,21 +165,23 @@ fail: #define test_bit(bit, array) (array[bit/8] & (1<<(bit%8))) -int pa__init(pa_core *c, pa_module*m) { +int pa__init(pa_module*m) { + pa_modargs *ma = NULL; struct userdata *u; int version; struct _input_id input_id; char name[256]; uint8_t evtype_bitmask[EV_MAX/8 + 1]; - assert(c && m); + + pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { pa_log("Failed to parse module arguments"); goto fail; } - m->userdata = u = pa_xmalloc(sizeof(struct userdata)); + m->userdata = u = pa_xnew(struct userdata,1); u->module = m; u->io = NULL; u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL)); @@ -221,11 +223,11 @@ int pa__init(pa_core *c, pa_module*m) { } if (!test_bit(EV_KEY, evtype_bitmask)) { - pa_log("device has no keys."); + pa_log("Device has no keys."); goto fail; } - u->io = c->mainloop->io_new(c->mainloop, u->fd, PA_IO_EVENT_INPUT|PA_IO_EVENT_HANGUP, io_callback, u); + u->io = m->core->mainloop->io_new(m->core->mainloop, u->fd, PA_IO_EVENT_INPUT|PA_IO_EVENT_HANGUP, io_callback, u); pa_modargs_free(ma); @@ -236,14 +238,14 @@ fail: if (ma) pa_modargs_free(ma); - pa__done(c, m); + pa__done(m); return -1; } -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module*m) { struct userdata *u; - assert(c); - assert(m); + + pa_assert(m); if (!(u = m->userdata)) return; @@ -252,7 +254,7 @@ void pa__done(pa_core *c, pa_module*m) { m->core->mainloop->io_free(u->io); if (u->fd >= 0) - close(u->fd); + pa_assert_se(pa_close(u->fd) == 0); pa_xfree(u->sink_name); pa_xfree(u); diff --git a/src/modules/module-native-protocol-fd.c b/src/modules/module-native-protocol-fd.c index 3c1c2bca..2ef61c88 100644 --- a/src/modules/module-native-protocol-fd.c +++ b/src/modules/module-native-protocol-fd.c @@ -26,10 +26,10 @@ #endif #include <stdio.h> -#include <assert.h> #include <unistd.h> #include <pulsecore/module.h> +#include <pulsecore/macro.h> #include <pulsecore/iochannel.h> #include <pulsecore/modargs.h> #include <pulsecore/protocol-native.h> @@ -48,25 +48,26 @@ static const char* const valid_modargs[] = { NULL, }; -int pa__init(pa_core *c, pa_module*m) { +int pa__init(pa_module*m) { pa_iochannel *io; pa_modargs *ma; int fd, r = -1; - assert(c && m); + + pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { - pa_log("failed to parse module arguments."); + pa_log("Failed to parse module arguments."); goto finish; } if (pa_modargs_get_value_s32(ma, "fd", &fd) < 0) { - pa_log("invalid file descriptor."); + pa_log("Invalid file descriptor."); goto finish; } - io = pa_iochannel_new(c->mainloop, fd, fd); + io = pa_iochannel_new(m->core->mainloop, fd, fd); - if (!(m->userdata = pa_protocol_native_new_iochannel(c, io, m, ma))) { + if (!(m->userdata = pa_protocol_native_new_iochannel(m->core, io, m, ma))) { pa_iochannel_free(io); goto finish; } @@ -80,8 +81,8 @@ finish: return r; } -void pa__done(pa_core *c, pa_module*m) { - assert(c && m); +void pa__done(pa_module*m) { + pa_assert(m); pa_protocol_native_free(m->userdata); } diff --git a/src/modules/module-null-sink.c b/src/modules/module-null-sink.c index 54a8e890..3a4edec7 100644 --- a/src/modules/module-null-sink.c +++ b/src/modules/module-null-sink.c @@ -28,7 +28,6 @@ #include <stdlib.h> #include <sys/stat.h> #include <stdio.h> -#include <assert.h> #include <errno.h> #include <string.h> #include <fcntl.h> @@ -38,12 +37,17 @@ #include <pulse/timeval.h> #include <pulse/xmalloc.h> -#include <pulsecore/iochannel.h> +#include <pulsecore/macro.h> #include <pulsecore/sink.h> #include <pulsecore/module.h> #include <pulsecore/core-util.h> +#include <pulsecore/core-error.h> #include <pulsecore/modargs.h> #include <pulsecore/log.h> +#include <pulsecore/thread.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/rtpoll.h> +#include <pulsecore/rtclock.h> #include "module-null-sink-symdef.h" @@ -64,11 +68,14 @@ struct userdata { pa_core *core; pa_module *module; pa_sink *sink; - pa_time_event *time_event; + + pa_thread *thread; + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; + size_t block_size; - uint64_t n_bytes; - struct timeval start_time; + struct timeval timestamp; }; static const char* const valid_modargs[] = { @@ -81,78 +88,132 @@ static const char* const valid_modargs[] = { NULL }; -static void time_callback(pa_mainloop_api *m, pa_time_event*e, const struct timeval *tv, void *userdata) { - struct userdata *u = userdata; - pa_memchunk chunk; - struct timeval ntv = *tv; - size_t l; +static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SINK(o)->userdata; + + switch (code) { + case PA_SINK_MESSAGE_SET_STATE: - assert(u); + if (PA_PTR_TO_UINT(data) == PA_SINK_RUNNING) + pa_rtclock_get(&u->timestamp); - if (pa_sink_render(u->sink, u->block_size, &chunk) >= 0) { - l = chunk.length; - pa_memblock_unref(chunk.memblock); - } else - l = u->block_size; + break; - pa_timeval_add(&ntv, pa_bytes_to_usec(l, &u->sink->sample_spec)); - m->time_restart(e, &ntv); + case PA_SINK_MESSAGE_GET_LATENCY: { + struct timeval now; - u->n_bytes += l; + pa_rtclock_get(&now); + + if (pa_timeval_cmp(&u->timestamp, &now) > 0) + *((pa_usec_t*) data) = 0; + else + *((pa_usec_t*) data) = pa_timeval_diff(&u->timestamp, &now); + break; + } + } + + return pa_sink_process_msg(o, code, data, offset, chunk); } -static pa_usec_t get_latency(pa_sink *s) { - struct userdata *u = s->userdata; - pa_usec_t a, b; - struct timeval now; +static void thread_func(void *userdata) { + struct userdata *u = userdata; + + pa_assert(u); + + pa_log_debug("Thread starting up"); + + pa_thread_mq_install(&u->thread_mq); + pa_rtpoll_install(u->rtpoll); + + pa_rtclock_get(&u->timestamp); + + for (;;) { + int ret; + + /* Render some data and drop it immediately */ + if (u->sink->thread_info.state == PA_SINK_RUNNING) { + struct timeval now; + + pa_rtclock_get(&now); - a = pa_timeval_diff(pa_gettimeofday(&now), &u->start_time); - b = pa_bytes_to_usec(u->n_bytes, &s->sample_spec); + if (pa_timeval_cmp(&u->timestamp, &now) <= 0) { + pa_sink_skip(u->sink, u->block_size); + pa_timeval_add(&u->timestamp, pa_bytes_to_usec(u->block_size, &u->sink->sample_spec)); + } - return b > a ? b - a : 0; + pa_rtpoll_set_timer_absolute(u->rtpoll, &u->timestamp); + } else + pa_rtpoll_set_timer_disabled(u->rtpoll); + + /* Hmm, nothing to do. Let's sleep */ + if ((ret = pa_rtpoll_run(u->rtpoll, 1)) < 0) + goto fail; + + if (ret == 0) + goto finish; + } + +fail: + /* If this was no regular exit from the loop we have to continue + * processing messages until we received PA_MESSAGE_SHUTDOWN */ + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + +finish: + pa_log_debug("Thread shutting down"); } -int pa__init(pa_core *c, pa_module*m) { +int pa__init(pa_module*m) { struct userdata *u = NULL; pa_sample_spec ss; pa_channel_map map; pa_modargs *ma = NULL; - assert(c); - assert(m); + pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { - pa_log("failed to parse module arguments."); + pa_log("Failed to parse module arguments."); goto fail; } - ss = c->default_sample_spec; + ss = m->core->default_sample_spec; if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) { - pa_log("invalid sample format specification or channel map."); + pa_log("Invalid sample format specification or channel map"); goto fail; } u = pa_xnew0(struct userdata, 1); - u->core = c; + u->core = m->core; u->module = m; m->userdata = u; + pa_thread_mq_init(&u->thread_mq, m->core->mainloop); + u->rtpoll = pa_rtpoll_new(); + pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq); - if (!(u->sink = pa_sink_new(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, &map))) { - pa_log("failed to create sink."); + if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, &map))) { + pa_log("Failed to create sink."); goto fail; } - u->sink->get_latency = get_latency; + u->sink->parent.process_msg = sink_process_msg; u->sink->userdata = u; - pa_sink_set_owner(u->sink, m); + u->sink->flags = PA_SINK_LATENCY; + + pa_sink_set_module(u->sink, m); + pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); + pa_sink_set_rtpoll(u->sink, u->rtpoll); pa_sink_set_description(u->sink, pa_modargs_get_value(ma, "description", "NULL sink")); - u->n_bytes = 0; - pa_gettimeofday(&u->start_time); + u->block_size = pa_bytes_per_second(&ss) / 20; /* 50 ms */ + if (u->block_size <= 0) + u->block_size = pa_frame_size(&ss); - u->time_event = c->mainloop->time_new(c->mainloop, &u->start_time, time_callback, u); + if (!(u->thread = pa_thread_new(thread_func, u))) { + pa_log("Failed to create thread."); + goto fail; + } - u->block_size = pa_bytes_per_second(&ss) / 10; + pa_sink_put(u->sink); pa_modargs_free(ma); @@ -162,22 +223,34 @@ fail: if (ma) pa_modargs_free(ma); - pa__done(c, m); + pa__done(m); return -1; } -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module*m) { struct userdata *u; - assert(c && m); + + pa_assert(m); if (!(u = m->userdata)) return; - pa_sink_disconnect(u->sink); - pa_sink_unref(u->sink); + if (u->sink) + pa_sink_unlink(u->sink); + + if (u->thread) { + pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); + pa_thread_free(u->thread); + } + + pa_thread_mq_done(&u->thread_mq); + + if (u->sink) + pa_sink_unref(u->sink); - u->core->mainloop->time_free(u->time_event); + if (u->rtpoll) + pa_rtpoll_free(u->rtpoll); pa_xfree(u); } diff --git a/src/modules/module-oss-mmap.c b/src/modules/module-oss-mmap.c deleted file mode 100644 index 16c9b311..00000000 --- a/src/modules/module-oss-mmap.c +++ /dev/null @@ -1,637 +0,0 @@ -/* $Id$ */ - -/*** - This file is part of PulseAudio. - - Copyright 2004-2006 Lennart Poettering - Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB - - PulseAudio is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published - by the Free Software Foundation; either version 2 of the License, - or (at your option) any later version. - - PulseAudio is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with PulseAudio; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - USA. -***/ - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <sys/soundcard.h> -#include <sys/ioctl.h> -#include <stdlib.h> -#include <sys/stat.h> -#include <stdio.h> -#include <assert.h> -#include <errno.h> -#include <string.h> -#include <fcntl.h> -#include <unistd.h> -#include <limits.h> - -#ifdef HAVE_SYS_MMAN_H -#include <sys/mman.h> -#endif - -#include <pulse/xmalloc.h> -#include <pulse/util.h> - -#include <pulsecore/core-error.h> -#include <pulsecore/iochannel.h> -#include <pulsecore/sink.h> -#include <pulsecore/source.h> -#include <pulsecore/module.h> -#include <pulsecore/sample-util.h> -#include <pulsecore/core-util.h> -#include <pulsecore/modargs.h> -#include <pulsecore/log.h> - -#include "oss-util.h" -#include "module-oss-mmap-symdef.h" - -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_DESCRIPTION("OSS Sink/Source (mmap)") -PA_MODULE_VERSION(PACKAGE_VERSION) -PA_MODULE_USAGE( - "sink_name=<name for the sink> " - "source_name=<name for the source> " - "device=<OSS device> " - "record=<enable source?> " - "playback=<enable sink?> " - "format=<sample format> " - "channels=<number of channels> " - "rate=<sample rate> " - "fragments=<number of fragments> " - "fragment_size=<fragment size> " - "channel_map=<channel map>") - -struct userdata { - pa_sink *sink; - pa_source *source; - pa_core *core; - pa_sample_spec sample_spec; - - size_t in_fragment_size, out_fragment_size; - unsigned in_fragments, out_fragments; - unsigned out_blocks_saved, in_blocks_saved; - - int fd; - - void *in_mmap, *out_mmap; - size_t in_mmap_length, out_mmap_length; - - pa_io_event *io_event; - - pa_memblock **in_memblocks, **out_memblocks; - unsigned out_current, in_current; - pa_module *module; -}; - -static const char* const valid_modargs[] = { - "sink_name", - "source_name", - "device", - "record", - "playback", - "fragments", - "fragment_size", - "format", - "rate", - "channels", - "channel_map", - NULL -}; - -#define DEFAULT_DEVICE "/dev/dsp" -#define DEFAULT_NFRAGS 12 -#define DEFAULT_FRAGSIZE 1024 - -static void update_usage(struct userdata *u) { - pa_module_set_used(u->module, - (u->sink ? pa_sink_used_by(u->sink) : 0) + - (u->source ? pa_source_used_by(u->source) : 0)); -} - -static void clear_up(struct userdata *u) { - assert(u); - - if (u->sink) { - pa_sink_disconnect(u->sink); - pa_sink_unref(u->sink); - u->sink = NULL; - } - - if (u->source) { - pa_source_disconnect(u->source); - pa_source_unref(u->source); - u->source = NULL; - } - - if (u->in_mmap && u->in_mmap != MAP_FAILED) { - munmap(u->in_mmap, u->in_mmap_length); - u->in_mmap = NULL; - } - - if (u->out_mmap && u->out_mmap != MAP_FAILED) { - munmap(u->out_mmap, u->out_mmap_length); - u->out_mmap = NULL; - } - - if (u->io_event) { - u->core->mainloop->io_free(u->io_event); - u->io_event = NULL; - } - - if (u->fd >= 0) { - close(u->fd); - u->fd = -1; - } -} - -static void out_fill_memblocks(struct userdata *u, unsigned n) { - assert(u && u->out_memblocks); - - while (n > 0) { - pa_memchunk chunk; - - if (u->out_memblocks[u->out_current]) - pa_memblock_unref_fixed(u->out_memblocks[u->out_current]); - - chunk.memblock = u->out_memblocks[u->out_current] = - pa_memblock_new_fixed( - u->core->mempool, - (uint8_t*) u->out_mmap+u->out_fragment_size*u->out_current, - u->out_fragment_size, - 1); - assert(chunk.memblock); - chunk.length = chunk.memblock->length; - chunk.index = 0; - - pa_sink_render_into_full(u->sink, &chunk); - - u->out_current++; - while (u->out_current >= u->out_fragments) - u->out_current -= u->out_fragments; - - n--; - } -} - -static void do_write(struct userdata *u) { - struct count_info info; - assert(u && u->sink); - - update_usage(u); - - if (ioctl(u->fd, SNDCTL_DSP_GETOPTR, &info) < 0) { - pa_log("SNDCTL_DSP_GETOPTR: %s", pa_cstrerror(errno)); - - clear_up(u); - pa_module_unload_request(u->module); - return; - } - - info.blocks += u->out_blocks_saved; - u->out_blocks_saved = 0; - - if (!info.blocks) - return; - - out_fill_memblocks(u, info.blocks); -} - -static void in_post_memblocks(struct userdata *u, unsigned n) { - assert(u && u->in_memblocks); - - while (n > 0) { - pa_memchunk chunk; - - if (!u->in_memblocks[u->in_current]) { - chunk.memblock = u->in_memblocks[u->in_current] = pa_memblock_new_fixed(u->core->mempool, (uint8_t*) u->in_mmap+u->in_fragment_size*u->in_current, u->in_fragment_size, 1); - chunk.length = chunk.memblock->length; - chunk.index = 0; - - pa_source_post(u->source, &chunk); - } - - u->in_current++; - while (u->in_current >= u->in_fragments) - u->in_current -= u->in_fragments; - - n--; - } -} - -static void in_clear_memblocks(struct userdata*u, unsigned n) { - unsigned i = u->in_current; - assert(u && u->in_memblocks); - - if (n > u->in_fragments) - n = u->in_fragments; - - while (n > 0) { - if (u->in_memblocks[i]) { - pa_memblock_unref_fixed(u->in_memblocks[i]); - u->in_memblocks[i] = NULL; - } - - i++; - while (i >= u->in_fragments) - i -= u->in_fragments; - - n--; - } -} - -static void do_read(struct userdata *u) { - struct count_info info; - assert(u && u->source); - - update_usage(u); - - if (ioctl(u->fd, SNDCTL_DSP_GETIPTR, &info) < 0) { - pa_log("SNDCTL_DSP_GETIPTR: %s", pa_cstrerror(errno)); - - clear_up(u); - pa_module_unload_request(u->module); - return; - } - - info.blocks += u->in_blocks_saved; - u->in_blocks_saved = 0; - - if (!info.blocks) - return; - - in_post_memblocks(u, info.blocks); - in_clear_memblocks(u, u->in_fragments/2); -} - -static void io_callback(pa_mainloop_api *m, pa_io_event *e, PA_GCC_UNUSED int fd, pa_io_event_flags_t f, void *userdata) { - struct userdata *u = userdata; - assert (u && u->core->mainloop == m && u->io_event == e); - - if (f & PA_IO_EVENT_ERROR) { - clear_up(u); - pa_module_unload_request(u->module); - return; - } - - if (f & PA_IO_EVENT_INPUT) - do_read(u); - if (f & PA_IO_EVENT_OUTPUT) - do_write(u); -} - -static pa_usec_t sink_get_latency_cb(pa_sink *s) { - struct userdata *u = s->userdata; - struct count_info info; - size_t bpos, n, total; - assert(s && u); - - if (ioctl(u->fd, SNDCTL_DSP_GETOPTR, &info) < 0) { - pa_log("SNDCTL_DSP_GETOPTR: %s", pa_cstrerror(errno)); - return 0; - } - - u->out_blocks_saved += info.blocks; - - total = u->out_fragments * u->out_fragment_size; - bpos = ((u->out_current + u->out_blocks_saved) * u->out_fragment_size) % total; - - if (bpos <= (size_t) info.ptr) - n = total - (info.ptr - bpos); - else - n = bpos - info.ptr; - -/* pa_log("n = %u, bpos = %u, ptr = %u, total=%u, fragsize = %u, n_frags = %u\n", n, bpos, (unsigned) info.ptr, total, u->out_fragment_size, u->out_fragments); */ - - return pa_bytes_to_usec(n, &s->sample_spec); -} - -static pa_usec_t source_get_latency_cb(pa_source *s) { - struct userdata *u = s->userdata; - struct count_info info; - size_t bpos, n, total; - assert(s && u); - - if (ioctl(u->fd, SNDCTL_DSP_GETIPTR, &info) < 0) { - pa_log("SNDCTL_DSP_GETIPTR: %s", pa_cstrerror(errno)); - return 0; - } - - u->in_blocks_saved += info.blocks; - - total = u->in_fragments * u->in_fragment_size; - bpos = ((u->in_current + u->in_blocks_saved) * u->in_fragment_size) % total; - - if (bpos <= (size_t) info.ptr) - n = info.ptr - bpos; - else - n = (u->in_fragments * u->in_fragment_size) - bpos + info.ptr; - -/* pa_log("n = %u, bpos = %u, ptr = %u, total=%u, fragsize = %u, n_frags = %u\n", n, bpos, (unsigned) info.ptr, total, u->in_fragment_size, u->in_fragments); */ - - return pa_bytes_to_usec(n, &s->sample_spec); -} - -static int sink_get_hw_volume(pa_sink *s) { - struct userdata *u = s->userdata; - - if (pa_oss_get_pcm_volume(u->fd, &s->sample_spec, &s->hw_volume) < 0) { - pa_log_info("device doesn't support reading mixer settings: %s", pa_cstrerror(errno)); - s->get_hw_volume = NULL; - return -1; - } - - return 0; -} - -static int sink_set_hw_volume(pa_sink *s) { - struct userdata *u = s->userdata; - - if (pa_oss_set_pcm_volume(u->fd, &s->sample_spec, &s->hw_volume) < 0) { - pa_log_info("device doesn't support writing mixer settings: %s", pa_cstrerror(errno)); - s->set_hw_volume = NULL; - return -1; - } - - return 0; -} - -static int source_get_hw_volume(pa_source *s) { - struct userdata *u = s->userdata; - - if (pa_oss_get_input_volume(u->fd, &s->sample_spec, &s->hw_volume) < 0) { - pa_log_info("device doesn't support reading mixer settings: %s", pa_cstrerror(errno)); - s->get_hw_volume = NULL; - return -1; - } - - return 0; -} - -static int source_set_hw_volume(pa_source *s) { - struct userdata *u = s->userdata; - - if (pa_oss_set_input_volume(u->fd, &s->sample_spec, &s->hw_volume) < 0) { - pa_log_info("device doesn't support writing mixer settings: %s", pa_cstrerror(errno)); - s->set_hw_volume = NULL; - return -1; - } - - return 0; -} - -int pa__init(pa_core *c, pa_module*m) { - struct audio_buf_info info; - struct userdata *u = NULL; - const char *p; - int nfrags, frag_size; - int mode, caps; - int enable_bits = 0, zero = 0; - int playback = 1, record = 1; - pa_modargs *ma = NULL; - char hwdesc[64], *t; - pa_channel_map map; - const char *name; - char *name_buf = NULL; - int namereg_fail; - - assert(c); - assert(m); - - m->userdata = u = pa_xnew0(struct userdata, 1); - u->module = m; - u->fd = -1; - u->core = c; - - if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { - pa_log("failed to parse module arguments."); - goto fail; - } - - if (pa_modargs_get_value_boolean(ma, "record", &record) < 0 || pa_modargs_get_value_boolean(ma, "playback", &playback) < 0) { - pa_log("record= and playback= expect numeric arguments."); - goto fail; - } - - if (!playback && !record) { - pa_log("neither playback nor record enabled for device."); - goto fail; - } - - mode = (playback&&record) ? O_RDWR : (playback ? O_WRONLY : (record ? O_RDONLY : 0)); - - nfrags = DEFAULT_NFRAGS; - frag_size = DEFAULT_FRAGSIZE; - if (pa_modargs_get_value_s32(ma, "fragments", &nfrags) < 0 || pa_modargs_get_value_s32(ma, "fragment_size", &frag_size) < 0) { - pa_log("failed to parse fragments arguments"); - goto fail; - } - - u->sample_spec = c->default_sample_spec; - if (pa_modargs_get_sample_spec_and_channel_map(ma, &u->sample_spec, &map, PA_CHANNEL_MAP_OSS) < 0) { - pa_log("failed to parse sample specification or channel map"); - goto fail; - } - - if ((u->fd = pa_oss_open(p = pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), &mode, &caps)) < 0) - goto fail; - - if (!(caps & DSP_CAP_MMAP) || !(caps & DSP_CAP_TRIGGER)) { - pa_log("OSS device not mmap capable."); - goto fail; - } - - pa_log_info("device opened in %s mode.", mode == O_WRONLY ? "O_WRONLY" : (mode == O_RDONLY ? "O_RDONLY" : "O_RDWR")); - - if (pa_oss_get_hw_description(p, hwdesc, sizeof(hwdesc)) >= 0) - pa_log_info("hardware name is '%s'.", hwdesc); - else - hwdesc[0] = 0; - - if (nfrags >= 2 && frag_size >= 1) - if (pa_oss_set_fragments(u->fd, nfrags, frag_size) < 0) - goto fail; - - if (pa_oss_auto_format(u->fd, &u->sample_spec) < 0) - goto fail; - - if (mode != O_WRONLY) { - if (ioctl(u->fd, SNDCTL_DSP_GETISPACE, &info) < 0) { - pa_log("SNDCTL_DSP_GETISPACE: %s", pa_cstrerror(errno)); - goto fail; - } - - pa_log_info("input -- %u fragments of size %u.", info.fragstotal, info.fragsize); - u->in_mmap_length = (u->in_fragment_size = info.fragsize) * (u->in_fragments = info.fragstotal); - - if ((u->in_mmap = mmap(NULL, u->in_mmap_length, PROT_READ, MAP_SHARED, u->fd, 0)) == MAP_FAILED) { - if (mode == O_RDWR) { - pa_log("mmap failed for input. Changing to O_WRONLY mode."); - mode = O_WRONLY; - } else { - pa_log("mmap(): %s", pa_cstrerror(errno)); - goto fail; - } - } else { - if ((name = pa_modargs_get_value(ma, "source_name", NULL))) - namereg_fail = 1; - else { - name = name_buf = pa_sprintf_malloc("oss_input.%s", pa_path_get_filename(p)); - namereg_fail = 0; - } - - if (!(u->source = pa_source_new(c, __FILE__, name, namereg_fail, &u->sample_spec, &map))) - goto fail; - - u->source->userdata = u; - u->source->get_latency = source_get_latency_cb; - u->source->get_hw_volume = source_get_hw_volume; - u->source->set_hw_volume = source_set_hw_volume; - pa_source_set_owner(u->source, m); - pa_source_set_description(u->source, t = pa_sprintf_malloc("OSS PCM/mmap() on %s%s%s%s", - p, - hwdesc[0] ? " (" : "", - hwdesc[0] ? hwdesc : "", - hwdesc[0] ? ")" : "")); - pa_xfree(t); - u->source->is_hardware = 1; - - u->in_memblocks = pa_xnew0(pa_memblock*, u->in_fragments); - - enable_bits |= PCM_ENABLE_INPUT; - } - } - - pa_xfree(name_buf); - name_buf = NULL; - - if (mode != O_RDONLY) { - if (ioctl(u->fd, SNDCTL_DSP_GETOSPACE, &info) < 0) { - pa_log("SNDCTL_DSP_GETOSPACE: %s", pa_cstrerror(errno)); - goto fail; - } - - pa_log_info("output -- %u fragments of size %u.", info.fragstotal, info.fragsize); - u->out_mmap_length = (u->out_fragment_size = info.fragsize) * (u->out_fragments = info.fragstotal); - - if ((u->out_mmap = mmap(NULL, u->out_mmap_length, PROT_WRITE, MAP_SHARED, u->fd, 0)) == MAP_FAILED) { - if (mode == O_RDWR) { - pa_log("mmap filed for output. Changing to O_RDONLY mode."); - mode = O_RDONLY; - } else { - pa_log("mmap(): %s", pa_cstrerror(errno)); - goto fail; - } - } else { - pa_silence_memory(u->out_mmap, u->out_mmap_length, &u->sample_spec); - - if ((name = pa_modargs_get_value(ma, "sink_name", NULL))) - namereg_fail = 1; - else { - name = name_buf = pa_sprintf_malloc("oss_output.%s", pa_path_get_filename(p)); - namereg_fail = 0; - } - - if (!(u->sink = pa_sink_new(c, __FILE__, name, namereg_fail, &u->sample_spec, &map))) - goto fail; - - u->sink->get_latency = sink_get_latency_cb; - u->sink->get_hw_volume = sink_get_hw_volume; - u->sink->set_hw_volume = sink_set_hw_volume; - u->sink->userdata = u; - pa_sink_set_owner(u->sink, m); - pa_sink_set_description(u->sink, t = pa_sprintf_malloc("OSS PCM/mmap() on %s%s%s%s", - p, - hwdesc[0] ? " (" : "", - hwdesc[0] ? hwdesc : "", - hwdesc[0] ? ")" : "")); - pa_xfree(t); - - u->sink->is_hardware = 1; - u->out_memblocks = pa_xmalloc0(sizeof(struct memblock *)*u->out_fragments); - - enable_bits |= PCM_ENABLE_OUTPUT; - } - } - - pa_xfree(name_buf); - name_buf = NULL; - - zero = 0; - if (ioctl(u->fd, SNDCTL_DSP_SETTRIGGER, &zero) < 0) { - pa_log("SNDCTL_DSP_SETTRIGGER: %s", pa_cstrerror(errno)); - goto fail; - } - - if (ioctl(u->fd, SNDCTL_DSP_SETTRIGGER, &enable_bits) < 0) { - pa_log("SNDCTL_DSP_SETTRIGGER: %s", pa_cstrerror(errno)); - goto fail; - } - - assert(u->source || u->sink); - - u->io_event = c->mainloop->io_new(c->mainloop, u->fd, (u->source ? PA_IO_EVENT_INPUT : 0) | (u->sink ? PA_IO_EVENT_OUTPUT : 0), io_callback, u); - assert(u->io_event); - - pa_modargs_free(ma); - - /* Read mixer settings */ - if (u->source) - source_get_hw_volume(u->source); - if (u->sink) - sink_get_hw_volume(u->sink); - - return 0; - -fail: - pa__done(c, m); - - if (ma) - pa_modargs_free(ma); - - pa_xfree(name_buf); - - return -1; -} - -void pa__done(pa_core *c, pa_module*m) { - struct userdata *u; - - assert(c); - assert(m); - - if (!(u = m->userdata)) - return; - - clear_up(u); - - if (u->out_memblocks) { - unsigned i; - for (i = 0; i < u->out_fragments; i++) - if (u->out_memblocks[i]) - pa_memblock_unref_fixed(u->out_memblocks[i]); - pa_xfree(u->out_memblocks); - } - - if (u->in_memblocks) { - unsigned i; - for (i = 0; i < u->in_fragments; i++) - if (u->in_memblocks[i]) - pa_memblock_unref_fixed(u->in_memblocks[i]); - pa_xfree(u->in_memblocks); - } - - pa_xfree(u); -} diff --git a/src/modules/module-oss.c b/src/modules/module-oss.c index 9d4d0eac..19dceef2 100644 --- a/src/modules/module-oss.c +++ b/src/modules/module-oss.c @@ -22,27 +22,46 @@ USA. ***/ +/* General power management rules: + * + * When SUSPENDED we close the audio device. + * + * We make no difference between IDLE and RUNNING in our handling. + * + * As long as we are in RUNNING/IDLE state we will *always* write data to + * the device. If none is avilable from the inputs, we write silence + * instead. + * + * If power should be saved on IDLE module-suspend-on-idle should be used. + * + */ + #ifdef HAVE_CONFIG_H #include <config.h> #endif +#ifdef HAVE_SYS_MMAN_H +#include <sys/mman.h> +#endif + #include <sys/soundcard.h> #include <sys/ioctl.h> #include <stdlib.h> #include <sys/stat.h> #include <stdio.h> -#include <assert.h> #include <errno.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <limits.h> +#include <signal.h> +#include <poll.h> #include <pulse/xmalloc.h> #include <pulse/util.h> #include <pulsecore/core-error.h> -#include <pulsecore/iochannel.h> +#include <pulsecore/thread.h> #include <pulsecore/sink.h> #include <pulsecore/source.h> #include <pulsecore/module.h> @@ -50,6 +69,9 @@ #include <pulsecore/core-util.h> #include <pulsecore/modargs.h> #include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/rtpoll.h> #include "oss-util.h" #include "module-oss-symdef.h" @@ -68,21 +90,48 @@ PA_MODULE_USAGE( "rate=<sample rate> " "fragments=<number of fragments> " "fragment_size=<fragment size> " - "channel_map=<channel map>") + "channel_map=<channel map> " + "mmap=<enable memory mapping?>") + +#define DEFAULT_DEVICE "/dev/dsp" struct userdata { + pa_core *core; + pa_module *module; pa_sink *sink; pa_source *source; - pa_iochannel *io; - pa_core *core; - pa_memchunk memchunk, silence; + pa_thread *thread; + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; + + char *device_name; + + pa_memchunk memchunk; - uint32_t in_fragment_size, out_fragment_size, sample_size; - int use_getospace, use_getispace; + size_t frame_size; + uint32_t in_fragment_size, out_fragment_size, in_nfrags, out_nfrags, in_hwbuf_size, out_hwbuf_size; + pa_bool_t use_getospace, use_getispace; + pa_bool_t use_getodelay; + + pa_bool_t sink_suspended, source_suspended; int fd; - pa_module *module; + int mode; + + int mixer_fd; + int mixer_devmask; + + int nfrags, frag_size; + + pa_bool_t use_mmap; + unsigned out_mmap_current, in_mmap_current; + void *in_mmap, *out_mmap; + pa_memblock **in_mmap_memblocks, **out_mmap_memblocks; + + int in_mmap_saved_nfrags, out_mmap_saved_nfrags; + + pa_rtpoll_item *rtpoll_item; }; static const char* const valid_modargs[] = { @@ -97,280 +146,1010 @@ static const char* const valid_modargs[] = { "rate", "channels", "channel_map", + "mmap", NULL }; -#define DEFAULT_DEVICE "/dev/dsp" +static void trigger(struct userdata *u, pa_bool_t quick) { + int enable_bits = 0, zero = 0; -static void update_usage(struct userdata *u) { - pa_module_set_used(u->module, - (u->sink ? pa_sink_used_by(u->sink) : 0) + - (u->source ? pa_source_used_by(u->source) : 0)); -} + pa_assert(u); -static void clear_up(struct userdata *u) { - assert(u); + if (u->fd < 0) + return; - if (u->sink) { - pa_sink_disconnect(u->sink); - pa_sink_unref(u->sink); - u->sink = NULL; - } + pa_log_debug("trigger"); - if (u->source) { - pa_source_disconnect(u->source); - pa_source_unref(u->source); - u->source = NULL; - } + if (u->source && PA_SOURCE_OPENED(u->source->thread_info.state)) + enable_bits |= PCM_ENABLE_INPUT; - if (u->io) { - pa_iochannel_free(u->io); - u->io = NULL; - } -} + if (u->sink && PA_SINK_OPENED(u->sink->thread_info.state)) + enable_bits |= PCM_ENABLE_OUTPUT; -static void do_write(struct userdata *u) { - pa_memchunk *memchunk; - ssize_t r; - size_t l; - int loop = 0; + pa_log_debug("trigger: %i", enable_bits); - assert(u); - if (!u->sink || !pa_iochannel_is_writable(u->io)) - return; + if (u->use_mmap) { - update_usage(u); + if (!quick) + ioctl(u->fd, SNDCTL_DSP_SETTRIGGER, &zero); - l = u->out_fragment_size; +#ifdef SNDCTL_DSP_HALT + if (enable_bits == 0) + if (ioctl(u->fd, SNDCTL_DSP_HALT, NULL) < 0) + pa_log_warn("SNDCTL_DSP_HALT: %s", pa_cstrerror(errno)); +#endif - if (u->use_getospace) { - audio_buf_info info; + if (ioctl(u->fd, SNDCTL_DSP_SETTRIGGER, &enable_bits) < 0) + pa_log_warn("SNDCTL_DSP_SETTRIGGER: %s", pa_cstrerror(errno)); - if (ioctl(u->fd, SNDCTL_DSP_GETOSPACE, &info) < 0) - u->use_getospace = 0; - else { - if (info.bytes/l > 0) { - l = (info.bytes/l)*l; - loop = 1; + if (u->sink && !(enable_bits & PCM_ENABLE_OUTPUT)) { + pa_log_debug("clearing playback buffer"); + pa_silence_memory(u->out_mmap, u->out_hwbuf_size, &u->sink->sample_spec); + } + + } else { + + if (enable_bits) + if (ioctl(u->fd, SNDCTL_DSP_POST, NULL) < 0) + pa_log_warn("SNDCTL_DSP_POST: %s", pa_cstrerror(errno)); + + if (!quick) { + /* + * Some crappy drivers do not start the recording until we + * read something. Without this snippet, poll will never + * register the fd as ready. + */ + + if (u->source && PA_SOURCE_OPENED(u->source->thread_info.state)) { + uint8_t *buf = pa_xnew(uint8_t, u->in_fragment_size); + pa_read(u->fd, buf, u->in_fragment_size, NULL); + pa_xfree(buf); } } } +} - do { - memchunk = &u->memchunk; +static void mmap_fill_memblocks(struct userdata *u, unsigned n) { + pa_assert(u); + pa_assert(u->out_mmap_memblocks); - if (!memchunk->length) - if (pa_sink_render(u->sink, l, memchunk) < 0) - memchunk = &u->silence; +/* pa_log("Mmmap writing %u blocks", n); */ - assert(memchunk->memblock); - assert(memchunk->memblock->data); - assert(memchunk->length); + while (n > 0) { + pa_memchunk chunk; - if ((r = pa_iochannel_write(u->io, (uint8_t*) memchunk->memblock->data + memchunk->index, memchunk->length)) < 0) { + if (u->out_mmap_memblocks[u->out_mmap_current]) + pa_memblock_unref_fixed(u->out_mmap_memblocks[u->out_mmap_current]); - if (errno != EAGAIN) { - pa_log("write() failed: %s", pa_cstrerror(errno)); + chunk.memblock = u->out_mmap_memblocks[u->out_mmap_current] = + pa_memblock_new_fixed( + u->core->mempool, + (uint8_t*) u->out_mmap + u->out_fragment_size * u->out_mmap_current, + u->out_fragment_size, + 1); - clear_up(u); - pa_module_unload_request(u->module); - } + chunk.length = pa_memblock_get_length(chunk.memblock); + chunk.index = 0; - break; - } + pa_sink_render_into_full(u->sink, &chunk); - if (memchunk == &u->silence) - assert(r % u->sample_size == 0); - else { - u->memchunk.index += r; - u->memchunk.length -= r; + u->out_mmap_current++; + while (u->out_mmap_current >= u->out_nfrags) + u->out_mmap_current -= u->out_nfrags; - if (u->memchunk.length <= 0) { - pa_memblock_unref(u->memchunk.memblock); - u->memchunk.memblock = NULL; - } - } + n--; + } +} + +static int mmap_write(struct userdata *u) { + struct count_info info; + + pa_assert(u); + pa_assert(u->sink); + +/* pa_log("Mmmap writing..."); */ + + if (ioctl(u->fd, SNDCTL_DSP_GETOPTR, &info) < 0) { + pa_log("SNDCTL_DSP_GETOPTR: %s", pa_cstrerror(errno)); + return -1; + } + + info.blocks += u->out_mmap_saved_nfrags; + u->out_mmap_saved_nfrags = 0; + + if (info.blocks > 0) + mmap_fill_memblocks(u, info.blocks); - l = l > (size_t) r ? l - r : 0; - } while (loop && l > 0); + return info.blocks; } -static void do_read(struct userdata *u) { - pa_memchunk memchunk; - ssize_t r; - size_t l; - int loop = 0; - assert(u); +static void mmap_post_memblocks(struct userdata *u, unsigned n) { + pa_assert(u); + pa_assert(u->in_mmap_memblocks); - if (!u->source || !pa_iochannel_is_readable(u->io) || !pa_idxset_size(u->source->outputs)) - return; +/* pa_log("Mmmap reading %u blocks", n); */ - update_usage(u); + while (n > 0) { + pa_memchunk chunk; - l = u->in_fragment_size; + if (!u->in_mmap_memblocks[u->in_mmap_current]) { - if (u->use_getispace) { - audio_buf_info info; + chunk.memblock = u->in_mmap_memblocks[u->in_mmap_current] = + pa_memblock_new_fixed( + u->core->mempool, + (uint8_t*) u->in_mmap + u->in_fragment_size*u->in_mmap_current, + u->in_fragment_size, + 1); - if (ioctl(u->fd, SNDCTL_DSP_GETISPACE, &info) < 0) - u->use_getispace = 0; - else { - if (info.bytes/l > 0) { - l = (info.bytes/l)*l; - loop = 1; - } + chunk.length = pa_memblock_get_length(chunk.memblock); + chunk.index = 0; + + pa_source_post(u->source, &chunk); } + + u->in_mmap_current++; + while (u->in_mmap_current >= u->in_nfrags) + u->in_mmap_current -= u->in_nfrags; + + n--; } +} - do { - memchunk.memblock = pa_memblock_new(u->core->mempool, l); - assert(memchunk.memblock); - if ((r = pa_iochannel_read(u->io, memchunk.memblock->data, memchunk.memblock->length)) < 0) { - pa_memblock_unref(memchunk.memblock); +static void mmap_clear_memblocks(struct userdata*u, unsigned n) { + unsigned i = u->in_mmap_current; - if (errno != EAGAIN) { - pa_log("read() failed: %s", pa_cstrerror(errno)); + pa_assert(u); + pa_assert(u->in_mmap_memblocks); - clear_up(u); - pa_module_unload_request(u->module); - } + if (n > u->in_nfrags) + n = u->in_nfrags; - break; + while (n > 0) { + if (u->in_mmap_memblocks[i]) { + pa_memblock_unref_fixed(u->in_mmap_memblocks[i]); + u->in_mmap_memblocks[i] = NULL; } - assert(r <= (ssize_t) memchunk.memblock->length); - memchunk.length = memchunk.memblock->length = r; - memchunk.index = 0; + i++; + while (i >= u->in_nfrags) + i -= u->in_nfrags; - pa_source_post(u->source, &memchunk); - pa_memblock_unref(memchunk.memblock); + n--; + } +} - l = l > (size_t) r ? l - r : 0; - } while (loop && l > 0); +static int mmap_read(struct userdata *u) { + struct count_info info; + pa_assert(u); + pa_assert(u->source); + +/* pa_log("Mmmap reading..."); */ + + if (ioctl(u->fd, SNDCTL_DSP_GETIPTR, &info) < 0) { + pa_log("SNDCTL_DSP_GETIPTR: %s", pa_cstrerror(errno)); + return -1; + } + +/* pa_log("... %i", info.blocks); */ + + info.blocks += u->in_mmap_saved_nfrags; + u->in_mmap_saved_nfrags = 0; + + if (info.blocks > 0) { + mmap_post_memblocks(u, info.blocks); + mmap_clear_memblocks(u, u->in_nfrags/2); + } + + return info.blocks; } -static void io_callback(PA_GCC_UNUSED pa_iochannel *io, void*userdata) { - struct userdata *u = userdata; - assert(u); - do_write(u); - do_read(u); +static pa_usec_t mmap_sink_get_latency(struct userdata *u) { + struct count_info info; + size_t bpos, n; + + pa_assert(u); + + if (ioctl(u->fd, SNDCTL_DSP_GETOPTR, &info) < 0) { + pa_log("SNDCTL_DSP_GETOPTR: %s", pa_cstrerror(errno)); + return 0; + } + + u->out_mmap_saved_nfrags += info.blocks; + + bpos = ((u->out_mmap_current + u->out_mmap_saved_nfrags) * u->out_fragment_size) % u->out_hwbuf_size; + + if (bpos <= (size_t) info.ptr) + n = u->out_hwbuf_size - (info.ptr - bpos); + else + n = bpos - info.ptr; + +/* pa_log("n = %u, bpos = %u, ptr = %u, total=%u, fragsize = %u, n_frags = %u\n", n, bpos, (unsigned) info.ptr, total, u->out_fragment_size, u->out_fragments); */ + + return pa_bytes_to_usec(n, &u->sink->sample_spec); } -static void source_notify_cb(pa_source *s) { - struct userdata *u = s->userdata; - assert(u); - do_read(u); +static pa_usec_t mmap_source_get_latency(struct userdata *u) { + struct count_info info; + size_t bpos, n; + + pa_assert(u); + + if (ioctl(u->fd, SNDCTL_DSP_GETIPTR, &info) < 0) { + pa_log("SNDCTL_DSP_GETIPTR: %s", pa_cstrerror(errno)); + return 0; + } + + u->in_mmap_saved_nfrags += info.blocks; + bpos = ((u->in_mmap_current + u->in_mmap_saved_nfrags) * u->in_fragment_size) % u->in_hwbuf_size; + + if (bpos <= (size_t) info.ptr) + n = info.ptr - bpos; + else + n = u->in_hwbuf_size - bpos + info.ptr; + +/* pa_log("n = %u, bpos = %u, ptr = %u, total=%u, fragsize = %u, n_frags = %u\n", n, bpos, (unsigned) info.ptr, total, u->in_fragment_size, u->in_fragments); */ + + return pa_bytes_to_usec(n, &u->source->sample_spec); } -static pa_usec_t sink_get_latency_cb(pa_sink *s) { +static pa_usec_t io_sink_get_latency(struct userdata *u) { pa_usec_t r = 0; - int arg; - struct userdata *u = s->userdata; - assert(s && u && u->sink); - if (ioctl(u->fd, SNDCTL_DSP_GETODELAY, &arg) < 0) { - pa_log_info("device doesn't support SNDCTL_DSP_GETODELAY: %s", pa_cstrerror(errno)); - s->get_latency = NULL; - return 0; + pa_assert(u); + + if (u->use_getodelay) { + int arg; + + if (ioctl(u->fd, SNDCTL_DSP_GETODELAY, &arg) < 0) { + pa_log_info("Device doesn't support SNDCTL_DSP_GETODELAY: %s", pa_cstrerror(errno)); + u->use_getodelay = 0; + } else + r = pa_bytes_to_usec(arg, &u->sink->sample_spec); + } - r += pa_bytes_to_usec(arg, &s->sample_spec); + if (!u->use_getodelay && u->use_getospace) { + struct audio_buf_info info; + + if (ioctl(u->fd, SNDCTL_DSP_GETOSPACE, &info) < 0) { + pa_log_info("Device doesn't support SNDCTL_DSP_GETOSPACE: %s", pa_cstrerror(errno)); + u->use_getospace = 0; + } else + r = pa_bytes_to_usec(info.bytes, &u->sink->sample_spec); + } if (u->memchunk.memblock) - r += pa_bytes_to_usec(u->memchunk.length, &s->sample_spec); + r += pa_bytes_to_usec(u->memchunk.length, &u->sink->sample_spec); return r; } -static pa_usec_t source_get_latency_cb(pa_source *s) { - struct userdata *u = s->userdata; - audio_buf_info info; - assert(s && u && u->source); - if (!u->use_getispace) - return 0; +static pa_usec_t io_source_get_latency(struct userdata *u) { + pa_usec_t r = 0; - if (ioctl(u->fd, SNDCTL_DSP_GETISPACE, &info) < 0) { - u->use_getispace = 0; - return 0; + pa_assert(u); + + if (u->use_getispace) { + struct audio_buf_info info; + + if (ioctl(u->fd, SNDCTL_DSP_GETISPACE, &info) < 0) { + pa_log_info("Device doesn't support SNDCTL_DSP_GETISPACE: %s", pa_cstrerror(errno)); + u->use_getispace = 0; + } else + r = pa_bytes_to_usec(info.bytes, &u->source->sample_spec); } - if (info.bytes <= 0) - return 0; + return r; +} + +static void build_pollfd(struct userdata *u) { + struct pollfd *pollfd; - return pa_bytes_to_usec(info.bytes, &s->sample_spec); + pa_assert(u); + pa_assert(u->fd >= 0); + + if (u->rtpoll_item) + pa_rtpoll_item_free(u->rtpoll_item); + + u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1); + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + pollfd->fd = u->fd; + pollfd->events = 0; + pollfd->revents = 0; } -static int sink_get_hw_volume(pa_sink *s) { - struct userdata *u = s->userdata; +static int suspend(struct userdata *u) { + pa_assert(u); + pa_assert(u->fd >= 0); - if (pa_oss_get_pcm_volume(u->fd, &s->sample_spec, &s->hw_volume) < 0) { - pa_log_info("device doesn't support reading mixer settings: %s", pa_cstrerror(errno)); - s->get_hw_volume = NULL; - return -1; + pa_log_info("Suspending..."); + + if (u->out_mmap_memblocks) { + unsigned i; + for (i = 0; i < u->out_nfrags; i++) + if (u->out_mmap_memblocks[i]) { + pa_memblock_unref_fixed(u->out_mmap_memblocks[i]); + u->out_mmap_memblocks[i] = NULL; + } } + if (u->in_mmap_memblocks) { + unsigned i; + for (i = 0; i < u->in_nfrags; i++) + if (u->in_mmap_memblocks[i]) { + pa_memblock_unref_fixed(u->in_mmap_memblocks[i]); + u->in_mmap_memblocks[i] = NULL; + } + } + + if (u->in_mmap && u->in_mmap != MAP_FAILED) { + munmap(u->in_mmap, u->in_hwbuf_size); + u->in_mmap = NULL; + } + + if (u->out_mmap && u->out_mmap != MAP_FAILED) { + munmap(u->out_mmap, u->out_hwbuf_size); + u->out_mmap = NULL; + } + + /* Let's suspend */ + ioctl(u->fd, SNDCTL_DSP_SYNC, NULL); + pa_close(u->fd); + u->fd = -1; + + if (u->rtpoll_item) { + pa_rtpoll_item_free(u->rtpoll_item); + u->rtpoll_item = NULL; + } + + pa_log_info("Device suspended..."); + return 0; } -static int sink_set_hw_volume(pa_sink *s) { - struct userdata *u = s->userdata; +static int unsuspend(struct userdata *u) { + int m; + pa_sample_spec ss, *ss_original; + int frag_size, in_frag_size, out_frag_size; + int in_nfrags, out_nfrags; + struct audio_buf_info info; + + pa_assert(u); + pa_assert(u->fd < 0); + + m = u->mode; + + pa_log_info("Trying resume..."); - if (pa_oss_set_pcm_volume(u->fd, &s->sample_spec, &s->hw_volume) < 0) { - pa_log_info("device doesn't support writing mixer settings: %s", pa_cstrerror(errno)); - s->set_hw_volume = NULL; + if ((u->fd = pa_oss_open(u->device_name, &m, NULL)) < 0) { + pa_log_warn("Resume failed, device busy (%s)", pa_cstrerror(errno)); return -1; + + if (m != u->mode) + pa_log_warn("Resume failed, couldn't open device with original access mode."); + goto fail; + } + + if (u->nfrags >= 2 && u->frag_size >= 1) + if (pa_oss_set_fragments(u->fd, u->nfrags, u->frag_size) < 0) { + pa_log_warn("Resume failed, couldn't set original fragment settings."); + goto fail; + } + + ss = *(ss_original = u->sink ? &u->sink->sample_spec : &u->source->sample_spec); + if (pa_oss_auto_format(u->fd, &ss) < 0 || !pa_sample_spec_equal(&ss, ss_original)) { + pa_log_warn("Resume failed, couldn't set original sample format settings."); + goto fail; + } + + if (ioctl(u->fd, SNDCTL_DSP_GETBLKSIZE, &frag_size) < 0) { + pa_log_warn("SNDCTL_DSP_GETBLKSIZE: %s", pa_cstrerror(errno)); + goto fail; + } + + in_frag_size = out_frag_size = frag_size; + in_nfrags = out_nfrags = u->nfrags; + + if (ioctl(u->fd, SNDCTL_DSP_GETISPACE, &info) >= 0) { + in_frag_size = info.fragsize; + in_nfrags = info.fragstotal; + } + + if (ioctl(u->fd, SNDCTL_DSP_GETOSPACE, &info) >= 0) { + out_frag_size = info.fragsize; + out_nfrags = info.fragstotal; + } + + if ((u->source && (in_frag_size != (int) u->in_fragment_size || in_nfrags != (int) u->in_nfrags)) || + (u->sink && (out_frag_size != (int) u->out_fragment_size || out_nfrags != (int) u->out_nfrags))) { + pa_log_warn("Resume failed, input fragment settings don't match."); + goto fail; + } + + if (u->use_mmap) { + if (u->source) { + if ((u->in_mmap = mmap(NULL, u->in_hwbuf_size, PROT_READ, MAP_SHARED, u->fd, 0)) == MAP_FAILED) { + pa_log("Resume failed, mmap(): %s", pa_cstrerror(errno)); + goto fail; + } + } + + if (u->sink) { + if ((u->out_mmap = mmap(NULL, u->out_hwbuf_size, PROT_WRITE, MAP_SHARED, u->fd, 0)) == MAP_FAILED) { + pa_log("Resume failed, mmap(): %s", pa_cstrerror(errno)); + if (u->in_mmap && u->in_mmap != MAP_FAILED) { + munmap(u->in_mmap, u->in_hwbuf_size); + u->in_mmap = NULL; + } + + goto fail; + } + + pa_silence_memory(u->out_mmap, u->out_hwbuf_size, &ss); + } } + u->out_mmap_current = u->in_mmap_current = 0; + u->out_mmap_saved_nfrags = u->in_mmap_saved_nfrags = 0; + + pa_assert(!u->rtpoll_item); + + build_pollfd(u); + + if (u->sink) + pa_sink_get_volume(u->sink); + if (u->source) + pa_source_get_volume(u->source); + + pa_log_info("Resumed successfully..."); + return 0; + +fail: + pa_close(u->fd); + u->fd = -1; + return -1; } -static int source_get_hw_volume(pa_source *s) { - struct userdata *u = s->userdata; +static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SINK(o)->userdata; + int ret; + pa_bool_t do_trigger = FALSE, quick = TRUE; + + switch (code) { + + case PA_SINK_MESSAGE_GET_LATENCY: { + pa_usec_t r = 0; + + if (u->fd >= 0) { + if (u->use_mmap) + r = mmap_sink_get_latency(u); + else + r = io_sink_get_latency(u); + } + + *((pa_usec_t*) data) = r; + + return 0; + } + + case PA_SINK_MESSAGE_SET_STATE: + + switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) { + + case PA_SINK_SUSPENDED: + pa_assert(PA_SINK_OPENED(u->sink->thread_info.state)); + + if (!u->source || u->source_suspended) { + if (suspend(u) < 0) + return -1; + } + + do_trigger = TRUE; + + u->sink_suspended = TRUE; + break; + + case PA_SINK_IDLE: + case PA_SINK_RUNNING: + + if (u->sink->thread_info.state == PA_SINK_INIT) { + do_trigger = TRUE; + quick = u->source && PA_SOURCE_OPENED(u->source->thread_info.state); + } + + if (u->sink->thread_info.state == PA_SINK_SUSPENDED) { + + if (!u->source || u->source_suspended) { + if (unsuspend(u) < 0) + return -1; + quick = FALSE; + } + + do_trigger = TRUE; + + u->out_mmap_current = 0; + u->out_mmap_saved_nfrags = 0; + + u->sink_suspended = FALSE; + } + + break; + + case PA_SINK_UNLINKED: + case PA_SINK_INIT: + ; + } + + break; - if (pa_oss_get_input_volume(u->fd, &s->sample_spec, &s->hw_volume) < 0) { - pa_log_info("device doesn't support reading mixer settings: %s", pa_cstrerror(errno)); - s->get_hw_volume = NULL; - return -1; } - return 0; + ret = pa_sink_process_msg(o, code, data, offset, chunk); + + if (do_trigger) + trigger(u, quick); + + return ret; } -static int source_set_hw_volume(pa_source *s) { - struct userdata *u = s->userdata; +static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SOURCE(o)->userdata; + int ret; + int do_trigger = FALSE, quick = TRUE; + + switch (code) { + + case PA_SOURCE_MESSAGE_GET_LATENCY: { + pa_usec_t r = 0; + + if (u->fd >= 0) { + if (u->use_mmap) + r = mmap_source_get_latency(u); + else + r = io_source_get_latency(u); + } + + *((pa_usec_t*) data) = r; + return 0; + } + + case PA_SOURCE_MESSAGE_SET_STATE: + + switch ((pa_source_state_t) PA_PTR_TO_UINT(data)) { + case PA_SOURCE_SUSPENDED: + pa_assert(PA_SOURCE_OPENED(u->source->thread_info.state)); + + if (!u->sink || u->sink_suspended) { + if (suspend(u) < 0) + return -1; + } + + do_trigger = TRUE; + + u->source_suspended = TRUE; + break; + + case PA_SOURCE_IDLE: + case PA_SOURCE_RUNNING: + + if (u->source->thread_info.state == PA_SOURCE_INIT) { + do_trigger = TRUE; + quick = u->sink && PA_SINK_OPENED(u->sink->thread_info.state); + } + + if (u->source->thread_info.state == PA_SOURCE_SUSPENDED) { + + if (!u->sink || u->sink_suspended) { + if (unsuspend(u) < 0) + return -1; + quick = FALSE; + } + + do_trigger = TRUE; + + u->in_mmap_current = 0; + u->in_mmap_saved_nfrags = 0; + + u->source_suspended = FALSE; + } + break; + + case PA_SOURCE_UNLINKED: + case PA_SOURCE_INIT: + ; + + } + break; - if (pa_oss_set_input_volume(u->fd, &s->sample_spec, &s->hw_volume) < 0) { - pa_log_info("device doesn't support writing mixer settings: %s", pa_cstrerror(errno)); - s->set_hw_volume = NULL; - return -1; } - return 0; + ret = pa_source_process_msg(o, code, data, offset, chunk); + + if (do_trigger) + trigger(u, quick); + + return ret; +} + +static int sink_get_volume(pa_sink *s) { + struct userdata *u; + int r; + + pa_assert_se(u = s->userdata); + + pa_assert(u->mixer_devmask & (SOUND_MASK_VOLUME|SOUND_MASK_PCM)); + + if (u->mixer_devmask & SOUND_MASK_VOLUME) + if ((r = pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_VOLUME, &s->sample_spec, &s->volume)) >= 0) + return r; + + if (u->mixer_devmask & SOUND_MASK_PCM) + if ((r = pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_PCM, &s->sample_spec, &s->volume)) >= 0) + return r; + + pa_log_info("Device doesn't support reading mixer settings: %s", pa_cstrerror(errno)); + return -1; +} + +static int sink_set_volume(pa_sink *s) { + struct userdata *u; + int r; + + pa_assert_se(u = s->userdata); + + pa_assert(u->mixer_devmask & (SOUND_MASK_VOLUME|SOUND_MASK_PCM)); + + if (u->mixer_devmask & SOUND_MASK_VOLUME) + if ((r = pa_oss_set_volume(u->mixer_fd, SOUND_MIXER_WRITE_VOLUME, &s->sample_spec, &s->volume)) >= 0) + return r; + + if (u->mixer_devmask & SOUND_MASK_PCM) + if ((r = pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_WRITE_PCM, &s->sample_spec, &s->volume)) >= 0) + return r; + + pa_log_info("Device doesn't support writing mixer settings: %s", pa_cstrerror(errno)); + return -1; } -int pa__init(pa_core *c, pa_module*m) { +static int source_get_volume(pa_source *s) { + struct userdata *u; + int r; + + pa_assert_se(u = s->userdata); + + pa_assert(u->mixer_devmask & (SOUND_MASK_IGAIN|SOUND_MASK_RECLEV)); + + if (u->mixer_devmask & SOUND_MASK_IGAIN) + if ((r = pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_IGAIN, &s->sample_spec, &s->volume)) >= 0) + return r; + + if (u->mixer_devmask & SOUND_MASK_RECLEV) + if ((r = pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_RECLEV, &s->sample_spec, &s->volume)) >= 0) + return r; + + pa_log_info("Device doesn't support reading mixer settings: %s", pa_cstrerror(errno)); + return -1; +} + +static int source_set_volume(pa_source *s) { + struct userdata *u; + int r; + + pa_assert_se(u = s->userdata); + + pa_assert(u->mixer_devmask & (SOUND_MASK_IGAIN|SOUND_MASK_RECLEV)); + + if (u->mixer_devmask & SOUND_MASK_IGAIN) + if ((r = pa_oss_set_volume(u->mixer_fd, SOUND_MIXER_WRITE_IGAIN, &s->sample_spec, &s->volume)) >= 0) + return r; + + if (u->mixer_devmask & SOUND_MASK_RECLEV) + if ((r = pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_WRITE_RECLEV, &s->sample_spec, &s->volume)) >= 0) + return r; + + pa_log_info("Device doesn't support writing mixer settings: %s", pa_cstrerror(errno)); + return -1; +} + +static void thread_func(void *userdata) { + struct userdata *u = userdata; + int write_type = 0, read_type = 0; + unsigned short revents = 0; + + pa_assert(u); + + pa_log_debug("Thread starting up"); + + if (u->core->high_priority) + pa_make_realtime(); + + pa_thread_mq_install(&u->thread_mq); + pa_rtpoll_install(u->rtpoll); + + for (;;) { + int ret; + +/* pa_log("loop"); */ + + /* Render some data and write it to the dsp */ + + if (u->sink && PA_SINK_OPENED(u->sink->thread_info.state) && ((revents & POLLOUT) || u->use_mmap || u->use_getospace)) { + + if (u->use_mmap) { + + if ((ret = mmap_write(u)) < 0) + goto fail; + + revents &= ~POLLOUT; + + if (ret > 0) + continue; + + } else { + ssize_t l; + pa_bool_t loop = FALSE, work_done = FALSE; + + l = u->out_fragment_size; + + if (u->use_getospace) { + audio_buf_info info; + + if (ioctl(u->fd, SNDCTL_DSP_GETOSPACE, &info) < 0) { + pa_log_info("Device doesn't support SNDCTL_DSP_GETOSPACE: %s", pa_cstrerror(errno)); + u->use_getospace = FALSE; + } else { + l = info.bytes; + + /* We loop only if GETOSPACE worked and we + * actually *know* that we can write more than + * one fragment at a time */ + loop = TRUE; + } + } + + /* Round down to multiples of the fragment size, + * because OSS needs that (at least some versions + * do) */ + l = (l/u->out_fragment_size) * u->out_fragment_size; + + /* Hmm, so poll() signalled us that we can read + * something, but GETOSPACE told us there was nothing? + * Hmm, make the best of it, try to read some data, to + * avoid spinning forever. */ + if (l <= 0 && (revents & POLLOUT)) { + l = u->out_fragment_size; + loop = FALSE; + } + + while (l > 0) { + void *p; + ssize_t t; + + if (u->memchunk.length <= 0) + pa_sink_render(u->sink, l, &u->memchunk); + + pa_assert(u->memchunk.length > 0); + + p = pa_memblock_acquire(u->memchunk.memblock); + t = pa_write(u->fd, (uint8_t*) p + u->memchunk.index, u->memchunk.length, &write_type); + pa_memblock_release(u->memchunk.memblock); + +/* pa_log("wrote %i bytes of %u", t, l); */ + + pa_assert(t != 0); + + if (t < 0) { + + if (errno == EINTR) + continue; + + else if (errno == EAGAIN) { + pa_log_debug("EAGAIN"); + + revents &= ~POLLOUT; + break; + + } else { + pa_log("Failed to write data to DSP: %s", pa_cstrerror(errno)); + goto fail; + } + + } else { + + u->memchunk.index += t; + u->memchunk.length -= t; + + if (u->memchunk.length <= 0) { + pa_memblock_unref(u->memchunk.memblock); + pa_memchunk_reset(&u->memchunk); + } + + l -= t; + + revents &= ~POLLOUT; + work_done = TRUE; + } + + if (!loop) + break; + } + + if (work_done) + continue; + } + } + + /* Try to read some data and pass it on to the source driver. */ + + if (u->source && PA_SOURCE_OPENED(u->source->thread_info.state) && ((revents & POLLIN) || u->use_mmap || u->use_getispace)) { + + if (u->use_mmap) { + + if ((ret = mmap_read(u)) < 0) + goto fail; + + revents &= ~POLLIN; + + if (ret > 0) + continue; + + } else { + + void *p; + ssize_t l; + pa_memchunk memchunk; + pa_bool_t loop = FALSE, work_done = FALSE; + + l = u->in_fragment_size; + + if (u->use_getispace) { + audio_buf_info info; + + if (ioctl(u->fd, SNDCTL_DSP_GETISPACE, &info) < 0) { + pa_log_info("Device doesn't support SNDCTL_DSP_GETISPACE: %s", pa_cstrerror(errno)); + u->use_getispace = FALSE; + } else { + l = info.bytes; + loop = TRUE; + } + } + + l = (l/u->in_fragment_size) * u->in_fragment_size; + + if (l <= 0 && (revents & POLLIN)) { + l = u->in_fragment_size; + loop = FALSE; + } + + while (l > 0) { + ssize_t t, k; + + pa_assert(l > 0); + + memchunk.memblock = pa_memblock_new(u->core->mempool, (size_t) -1); + + k = pa_memblock_get_length(memchunk.memblock); + + if (k > l) + k = l; + + k = (k/u->frame_size)*u->frame_size; + + p = pa_memblock_acquire(memchunk.memblock); + t = pa_read(u->fd, p, k, &read_type); + pa_memblock_release(memchunk.memblock); + + pa_assert(t != 0); /* EOF cannot happen */ + +/* pa_log("read %i bytes of %u", t, l); */ + + if (t < 0) { + pa_memblock_unref(memchunk.memblock); + + if (errno == EINTR) + continue; + + else if (errno == EAGAIN) { + pa_log_debug("EAGAIN"); + + revents &= ~POLLIN; + break; + + } else { + pa_log("Failed to read data from DSP: %s", pa_cstrerror(errno)); + goto fail; + } + + } else { + memchunk.index = 0; + memchunk.length = t; + + pa_source_post(u->source, &memchunk); + pa_memblock_unref(memchunk.memblock); + + l -= t; + + revents &= ~POLLIN; + work_done = TRUE; + } + + if (!loop) + break; + } + + if (work_done) + continue; + } + } + +/* pa_log("loop2 revents=%i", revents); */ + + if (u->rtpoll_item) { + struct pollfd *pollfd; + + pa_assert(u->fd >= 0); + + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + pollfd->events = + ((u->source && PA_SOURCE_OPENED(u->source->thread_info.state)) ? POLLIN : 0) | + ((u->sink && PA_SINK_OPENED(u->sink->thread_info.state)) ? POLLOUT : 0); + } + + /* Hmm, nothing to do. Let's sleep */ + if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) + goto fail; + + if (ret == 0) + goto finish; + + if (u->rtpoll_item) { + struct pollfd *pollfd; + + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + + if (pollfd->revents & ~(POLLOUT|POLLIN)) { + pa_log("DSP shutdown."); + goto fail; + } + + revents = pollfd->revents; + } else + revents = 0; + } + +fail: + /* If this was no regular exit from the loop we have to continue + * processing messages until we received PA_MESSAGE_SHUTDOWN */ + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + +finish: + pa_log_debug("Thread shutting down"); +} + +int pa__init(pa_module*m) { + struct audio_buf_info info; struct userdata *u = NULL; - const char *p; + const char *dev; int fd = -1; - int nfrags, frag_size, in_frag_size, out_frag_size; - int mode; - int record = 1, playback = 1; + int nfrags, frag_size; + int mode, caps; + int record = 1, playback = 1, use_mmap = 1; pa_sample_spec ss; pa_channel_map map; pa_modargs *ma = NULL; char hwdesc[64], *t; const char *name; - char *name_buf = NULL; int namereg_fail; - assert(c); - assert(m); + pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { - pa_log("failed to parse module arguments."); + pa_log("Failed to parse module arguments."); goto fail; } @@ -380,36 +1159,52 @@ int pa__init(pa_core *c, pa_module*m) { } if (!playback && !record) { - pa_log("neither playback nor record enabled for device."); + pa_log("Neither playback nor record enabled for device."); goto fail; } - mode = (playback&&record) ? O_RDWR : (playback ? O_WRONLY : (record ? O_RDONLY : 0)); + mode = (playback && record) ? O_RDWR : (playback ? O_WRONLY : (record ? O_RDONLY : 0)); - ss = c->default_sample_spec; + ss = m->core->default_sample_spec; if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_OSS) < 0) { - pa_log("failed to parse sample specification or channel map"); + pa_log("Failed to parse sample specification or channel map"); goto fail; } - /* Fix latency to 100ms */ - nfrags = 12; - frag_size = pa_bytes_per_second(&ss)/128; + nfrags = m->core->default_n_fragments; + frag_size = pa_usec_to_bytes(m->core->default_fragment_size_msec*1000, &ss); + if (frag_size <= 0) + frag_size = pa_frame_size(&ss); if (pa_modargs_get_value_s32(ma, "fragments", &nfrags) < 0 || pa_modargs_get_value_s32(ma, "fragment_size", &frag_size) < 0) { - pa_log("failed to parse fragments arguments"); + pa_log("Failed to parse fragments arguments"); goto fail; } - if ((fd = pa_oss_open(p = pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), &mode, NULL)) < 0) + if (pa_modargs_get_value_boolean(ma, "mmap", &use_mmap) < 0) { + pa_log("Failed to parse mmap argument."); goto fail; + } - if (pa_oss_get_hw_description(p, hwdesc, sizeof(hwdesc)) >= 0) - pa_log_info("hardware name is '%s'.", hwdesc); + if ((fd = pa_oss_open(dev = pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), &mode, &caps)) < 0) + goto fail; + + if (use_mmap && (!(caps & DSP_CAP_MMAP) || !(caps & DSP_CAP_TRIGGER))) { + pa_log_info("OSS device not mmap capable, falling back to UNIX read/write mode."); + use_mmap = 0; + } + + if (use_mmap && mode == O_WRONLY) { + pa_log_info("Device opened for playback only, cannot do memory mapping, falling back to UNIX write() mode."); + use_mmap = 0; + } + + if (pa_oss_get_hw_description(dev, hwdesc, sizeof(hwdesc)) >= 0) + pa_log_info("Hardware name is '%s'.", hwdesc); else hwdesc[0] = 0; - pa_log_info("device opened in %s mode.", mode == O_WRONLY ? "O_WRONLY" : (mode == O_RDONLY ? "O_RDONLY" : "O_RDWR")); + pa_log_info("Device opened in %s mode.", mode == O_WRONLY ? "O_WRONLY" : (mode == O_RDONLY ? "O_RDONLY" : "O_RDWR")); if (nfrags >= 2 && frag_size >= 1) if (pa_oss_set_fragments(fd, nfrags, frag_size) < 0) @@ -422,152 +1217,282 @@ int pa__init(pa_core *c, pa_module*m) { pa_log("SNDCTL_DSP_GETBLKSIZE: %s", pa_cstrerror(errno)); goto fail; } - assert(frag_size); - in_frag_size = out_frag_size = frag_size; + pa_assert(frag_size > 0); - u = pa_xmalloc(sizeof(struct userdata)); - u->core = c; - u->use_getospace = u->use_getispace = 0; + u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + m->userdata = u; + u->fd = fd; + u->mixer_fd = -1; + u->use_getospace = u->use_getispace = 1; + u->use_getodelay = 1; + u->mode = mode; + u->frame_size = pa_frame_size(&ss); + u->device_name = pa_xstrdup(dev); + u->in_nfrags = u->out_nfrags = u->nfrags = nfrags; + u->out_fragment_size = u->in_fragment_size = u->frag_size = frag_size; + u->use_mmap = use_mmap; + pa_thread_mq_init(&u->thread_mq, m->core->mainloop); + u->rtpoll = pa_rtpoll_new(); + pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq); + u->rtpoll_item = NULL; + build_pollfd(u); if (ioctl(fd, SNDCTL_DSP_GETISPACE, &info) >= 0) { - pa_log_info("input -- %u fragments of size %u.", info.fragstotal, info.fragsize); - in_frag_size = info.fragsize; + pa_log_info("Input -- %u fragments of size %u.", info.fragstotal, info.fragsize); + u->in_fragment_size = info.fragsize; + u->in_nfrags = info.fragstotal; u->use_getispace = 1; } if (ioctl(fd, SNDCTL_DSP_GETOSPACE, &info) >= 0) { - pa_log_info("output -- %u fragments of size %u.", info.fragstotal, info.fragsize); - out_frag_size = info.fragsize; + pa_log_info("Output -- %u fragments of size %u.", info.fragstotal, info.fragsize); + u->out_fragment_size = info.fragsize; + u->out_nfrags = info.fragstotal; u->use_getospace = 1; } + u->in_hwbuf_size = u->in_nfrags * u->in_fragment_size; + u->out_hwbuf_size = u->out_nfrags * u->out_fragment_size; + if (mode != O_WRONLY) { + char *name_buf = NULL; + + if (use_mmap) { + if ((u->in_mmap = mmap(NULL, u->in_hwbuf_size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) { + pa_log_warn("mmap(PROT_READ) failed, reverting to non-mmap mode: %s", pa_cstrerror(errno)); + use_mmap = u->use_mmap = 0; + u->in_mmap = NULL; + } else + pa_log_debug("Successfully mmap()ed input buffer."); + } + if ((name = pa_modargs_get_value(ma, "source_name", NULL))) namereg_fail = 1; else { - name = name_buf = pa_sprintf_malloc("oss_input.%s", pa_path_get_filename(p)); + name = name_buf = pa_sprintf_malloc("oss_input.%s", pa_path_get_filename(dev)); namereg_fail = 0; } - if (!(u->source = pa_source_new(c, __FILE__, name, namereg_fail, &ss, &map))) + u->source = pa_source_new(m->core, __FILE__, name, namereg_fail, &ss, &map); + pa_xfree(name_buf); + if (!u->source) { + pa_log("Failed to create source object"); goto fail; + } + u->source->parent.process_msg = source_process_msg; u->source->userdata = u; - u->source->notify = source_notify_cb; - u->source->get_latency = source_get_latency_cb; - u->source->get_hw_volume = source_get_hw_volume; - u->source->set_hw_volume = source_set_hw_volume; - pa_source_set_owner(u->source, m); - pa_source_set_description(u->source, t = pa_sprintf_malloc("OSS PCM on %s%s%s%s", - p, - hwdesc[0] ? " (" : "", - hwdesc[0] ? hwdesc : "", - hwdesc[0] ? ")" : "")); + + pa_source_set_module(u->source, m); + pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); + pa_source_set_rtpoll(u->source, u->rtpoll); + pa_source_set_description(u->source, t = pa_sprintf_malloc( + "OSS PCM on %s%s%s%s%s", + dev, + hwdesc[0] ? " (" : "", + hwdesc[0] ? hwdesc : "", + hwdesc[0] ? ")" : "", + use_mmap ? " via DMA" : "")); pa_xfree(t); - u->source->is_hardware = 1; - } else - u->source = NULL; + u->source->flags = PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY; + u->source->refresh_volume = TRUE; - pa_xfree(name_buf); - name_buf = NULL; + if (use_mmap) + u->in_mmap_memblocks = pa_xnew0(pa_memblock*, u->in_nfrags); + } if (mode != O_RDONLY) { + char *name_buf = NULL; + + if (use_mmap) { + if ((u->out_mmap = mmap(NULL, u->out_hwbuf_size, PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) { + if (mode == O_RDWR) { + pa_log_debug("mmap() failed for input. Changing to O_WRONLY mode."); + mode = O_WRONLY; + goto go_on; + } else { + pa_log_warn("mmap(PROT_WRITE) failed, reverting to non-mmap mode: %s", pa_cstrerror(errno)); + u->use_mmap = (use_mmap = FALSE); + u->out_mmap = NULL; + } + } else { + pa_log_debug("Successfully mmap()ed output buffer."); + pa_silence_memory(u->out_mmap, u->out_hwbuf_size, &ss); + } + } + if ((name = pa_modargs_get_value(ma, "sink_name", NULL))) namereg_fail = 1; else { - name = name_buf = pa_sprintf_malloc("oss_output.%s", pa_path_get_filename(p)); + name = name_buf = pa_sprintf_malloc("oss_output.%s", pa_path_get_filename(dev)); namereg_fail = 0; } - if (!(u->sink = pa_sink_new(c, __FILE__, name, namereg_fail, &ss, &map))) + u->sink = pa_sink_new(m->core, __FILE__, name, namereg_fail, &ss, &map); + pa_xfree(name_buf); + if (!u->sink) { + pa_log("Failed to create sink object"); goto fail; + } - u->sink->get_latency = sink_get_latency_cb; - u->sink->get_hw_volume = sink_get_hw_volume; - u->sink->set_hw_volume = sink_set_hw_volume; + u->sink->parent.process_msg = sink_process_msg; u->sink->userdata = u; - pa_sink_set_owner(u->sink, m); - pa_sink_set_description(u->sink, t = pa_sprintf_malloc("OSS PCM on %s%s%s%s", - p, - hwdesc[0] ? " (" : "", - hwdesc[0] ? hwdesc : "", - hwdesc[0] ? ")" : "")); + + pa_sink_set_module(u->sink, m); + pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); + pa_sink_set_rtpoll(u->sink, u->rtpoll); + pa_sink_set_description(u->sink, t = pa_sprintf_malloc( + "OSS PCM on %s%s%s%s%s", + dev, + hwdesc[0] ? " (" : "", + hwdesc[0] ? hwdesc : "", + hwdesc[0] ? ")" : "", + use_mmap ? " via DMA" : "")); pa_xfree(t); - u->sink->is_hardware = 1; - } else - u->sink = NULL; + u->sink->flags = PA_SINK_HARDWARE|PA_SINK_LATENCY; + u->sink->refresh_volume = TRUE; - pa_xfree(name_buf); - name_buf = NULL; + if (use_mmap) + u->out_mmap_memblocks = pa_xnew0(pa_memblock*, u->out_nfrags); + } - assert(u->source || u->sink); + if ((u->mixer_fd = pa_oss_open_mixer_for_device(u->device_name)) >= 0) { + int do_close = 1; + u->mixer_devmask = 0; - u->io = pa_iochannel_new(c->mainloop, u->source ? fd : -1, u->sink ? fd : -1); - assert(u->io); - pa_iochannel_set_callback(u->io, io_callback, u); - u->fd = fd; + if (ioctl(fd, SOUND_MIXER_READ_DEVMASK, &u->mixer_devmask) < 0) + pa_log_warn("SOUND_MIXER_READ_DEVMASK failed: %s", pa_cstrerror(errno)); - u->memchunk.memblock = NULL; - u->memchunk.length = 0; - u->sample_size = pa_frame_size(&ss); + else { + if (u->sink && (u->mixer_devmask & (SOUND_MASK_VOLUME|SOUND_MASK_PCM))) { + pa_log_debug("Found hardware mixer track for playback."); + u->sink->flags |= PA_SINK_HW_VOLUME_CTRL; + u->sink->get_volume = sink_get_volume; + u->sink->set_volume = sink_set_volume; + do_close = 0; + } - u->out_fragment_size = out_frag_size; - u->in_fragment_size = in_frag_size; - u->silence.memblock = pa_memblock_new(u->core->mempool, u->silence.length = u->out_fragment_size); - assert(u->silence.memblock); - pa_silence_memblock(u->silence.memblock, &ss); - u->silence.index = 0; + if (u->source && (u->mixer_devmask & (SOUND_MASK_RECLEV|SOUND_MASK_IGAIN))) { + pa_log_debug("Found hardware mixer track for recording."); + u->source->flags |= PA_SOURCE_HW_VOLUME_CTRL; + u->source->get_volume = source_get_volume; + u->source->set_volume = source_set_volume; + do_close = 0; + } + } - u->module = m; - m->userdata = u; + if (do_close) { + pa_close(u->mixer_fd); + u->mixer_fd = -1; + } + } - pa_modargs_free(ma); +go_on: + + pa_assert(u->source || u->sink); - /* - * Some crappy drivers do not start the recording until we read something. - * Without this snippet, poll will never register the fd as ready. - */ - if (u->source) { - char *buf = pa_xnew(char, u->sample_size); - pa_read(u->fd, buf, u->sample_size, NULL); - pa_xfree(buf); + pa_memchunk_reset(&u->memchunk); + + if (!(u->thread = pa_thread_new(thread_func, u))) { + pa_log("Failed to create thread."); + goto fail; } /* Read mixer settings */ - if (u->source) - source_get_hw_volume(u->source); + if (u->sink && u->sink->get_volume) + sink_get_volume(u->sink); + if (u->source && u->source->get_volume) + source_get_volume(u->source); + if (u->sink) - sink_get_hw_volume(u->sink); + pa_sink_put(u->sink); + if (u->source) + pa_source_put(u->source); + + pa_modargs_free(ma); return 0; fail: - if (fd >= 0) - close(fd); + + if (u) + pa__done(m); + else if (fd >= 0) + pa_close(fd); if (ma) pa_modargs_free(ma); - pa_xfree(name_buf); - return -1; } -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module*m) { struct userdata *u; - assert(c); - assert(m); + pa_assert(m); if (!(u = m->userdata)) return; - clear_up(u); + if (u->sink) + pa_sink_unlink(u->sink); + + if (u->source) + pa_source_unlink(u->source); + + if (u->thread) { + pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); + pa_thread_free(u->thread); + } + + pa_thread_mq_done(&u->thread_mq); + + if (u->sink) + pa_sink_unref(u->sink); + + if (u->source) + pa_source_unref(u->source); if (u->memchunk.memblock) pa_memblock_unref(u->memchunk.memblock); - if (u->silence.memblock) - pa_memblock_unref(u->silence.memblock); + + if (u->rtpoll_item) + pa_rtpoll_item_free(u->rtpoll_item); + + if (u->rtpoll) + pa_rtpoll_free(u->rtpoll); + + if (u->out_mmap_memblocks) { + unsigned i; + for (i = 0; i < u->out_nfrags; i++) + if (u->out_mmap_memblocks[i]) + pa_memblock_unref_fixed(u->out_mmap_memblocks[i]); + pa_xfree(u->out_mmap_memblocks); + } + + if (u->in_mmap_memblocks) { + unsigned i; + for (i = 0; i < u->in_nfrags; i++) + if (u->in_mmap_memblocks[i]) + pa_memblock_unref_fixed(u->in_mmap_memblocks[i]); + pa_xfree(u->in_mmap_memblocks); + } + + if (u->in_mmap && u->in_mmap != MAP_FAILED) + munmap(u->in_mmap, u->in_hwbuf_size); + + if (u->out_mmap && u->out_mmap != MAP_FAILED) + munmap(u->out_mmap, u->out_hwbuf_size); + + if (u->fd >= 0) + pa_close(u->fd); + + if (u->mixer_fd >= 0) + pa_close(u->mixer_fd); + + pa_xfree(u->device_name); pa_xfree(u); } diff --git a/src/modules/module-pipe-sink.c b/src/modules/module-pipe-sink.c index 170b046e..75748474 100644 --- a/src/modules/module-pipe-sink.c +++ b/src/modules/module-pipe-sink.c @@ -28,22 +28,25 @@ #include <stdlib.h> #include <sys/stat.h> #include <stdio.h> -#include <assert.h> #include <errno.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <limits.h> +#include <sys/ioctl.h> +#include <poll.h> #include <pulse/xmalloc.h> #include <pulsecore/core-error.h> -#include <pulsecore/iochannel.h> #include <pulsecore/sink.h> #include <pulsecore/module.h> #include <pulsecore/core-util.h> #include <pulsecore/modargs.h> #include <pulsecore/log.h> +#include <pulsecore/thread.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/rtpoll.h> #include "module-pipe-sink-symdef.h" @@ -58,20 +61,24 @@ PA_MODULE_USAGE( "rate=<sample rate>" "channel_map=<channel map>") -#define DEFAULT_FIFO_NAME "/tmp/music.output" +#define DEFAULT_FILE_NAME "/tmp/music.output" #define DEFAULT_SINK_NAME "fifo_output" struct userdata { pa_core *core; + pa_module *module; + pa_sink *sink; - char *filename; + pa_thread *thread; + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; - pa_sink *sink; - pa_iochannel *io; - pa_defer_event *defer_event; + char *filename; + int fd; pa_memchunk memchunk; - pa_module *module; + + pa_rtpoll_item *rtpoll_item; }; static const char* const valid_modargs[] = { @@ -84,133 +91,191 @@ static const char* const valid_modargs[] = { NULL }; -static void do_write(struct userdata *u) { - ssize_t r; - assert(u); +static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SINK(o)->userdata; - u->core->mainloop->defer_enable(u->defer_event, 0); + switch (code) { - if (!pa_iochannel_is_writable(u->io)) - return; + case PA_SINK_MESSAGE_GET_LATENCY: { + size_t n = 0; + int l; - pa_module_set_used(u->module, pa_sink_used_by(u->sink)); - - if (!u->memchunk.length) - if (pa_sink_render(u->sink, PIPE_BUF, &u->memchunk) < 0) - return; +#ifdef TIOCINQ + if (ioctl(u->fd, TIOCINQ, &l) >= 0 && l > 0) + n = (size_t) l; +#endif - assert(u->memchunk.memblock && u->memchunk.length); + n += u->memchunk.length; - if ((r = pa_iochannel_write(u->io, (uint8_t*) u->memchunk.memblock->data + u->memchunk.index, u->memchunk.length)) < 0) { - pa_log("write(): %s", pa_cstrerror(errno)); - return; + *((pa_usec_t*) data) = pa_bytes_to_usec(n, &u->sink->sample_spec); + break; + } } - u->memchunk.index += r; - u->memchunk.length -= r; - - if (u->memchunk.length <= 0) { - pa_memblock_unref(u->memchunk.memblock); - u->memchunk.memblock = NULL; - } + return pa_sink_process_msg(o, code, data, offset, chunk); } -static void notify_cb(pa_sink*s) { - struct userdata *u = s->userdata; - assert(s && u); +static void thread_func(void *userdata) { + struct userdata *u = userdata; + int write_type = 0; - if (pa_iochannel_is_writable(u->io)) - u->core->mainloop->defer_enable(u->defer_event, 1); -} + pa_assert(u); -static pa_usec_t get_latency_cb(pa_sink *s) { - struct userdata *u = s->userdata; - assert(s && u); + pa_log_debug("Thread starting up"); - return u->memchunk.memblock ? pa_bytes_to_usec(u->memchunk.length, &s->sample_spec) : 0; -} + pa_thread_mq_install(&u->thread_mq); + pa_rtpoll_install(u->rtpoll); -static void defer_callback(PA_GCC_UNUSED pa_mainloop_api *m, PA_GCC_UNUSED pa_defer_event*e, void *userdata) { - struct userdata *u = userdata; - assert(u); - do_write(u); -} + for (;;) { + struct pollfd *pollfd; + int ret; -static void io_callback(PA_GCC_UNUSED pa_iochannel *io, void*userdata) { - struct userdata *u = userdata; - assert(u); - do_write(u); + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + + /* Render some data and write it to the fifo */ + if (u->sink->thread_info.state == PA_SINK_RUNNING && pollfd->revents) { + ssize_t l; + void *p; + + if (u->memchunk.length <= 0) + pa_sink_render(u->sink, PIPE_BUF, &u->memchunk); + + pa_assert(u->memchunk.length > 0); + + p = pa_memblock_acquire(u->memchunk.memblock); + l = pa_write(u->fd, (uint8_t*) p + u->memchunk.index, u->memchunk.length, &write_type); + pa_memblock_release(u->memchunk.memblock); + + pa_assert(l != 0); + + if (l < 0) { + + if (errno == EINTR) + continue; + else if (errno != EAGAIN) { + pa_log("Failed to write data to FIFO: %s", pa_cstrerror(errno)); + goto fail; + } + + } else { + + u->memchunk.index += l; + u->memchunk.length -= l; + + if (u->memchunk.length <= 0) { + pa_memblock_unref(u->memchunk.memblock); + pa_memchunk_reset(&u->memchunk); + } + + pollfd->revents = 0; + } + } + + /* Hmm, nothing to do. Let's sleep */ + pollfd->events = u->sink->thread_info.state == PA_SINK_RUNNING ? POLLOUT : 0; + + if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) + goto fail; + + if (ret == 0) + goto finish; + + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + + if (pollfd->revents & ~POLLOUT) { + pa_log("FIFO shutdown."); + goto fail; + } + } + +fail: + /* If this was no regular exit from the loop we have to continue + * processing messages until we received PA_MESSAGE_SHUTDOWN */ + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + +finish: + pa_log_debug("Thread shutting down"); } -int pa__init(pa_core *c, pa_module*m) { - struct userdata *u = NULL; +int pa__init(pa_module*m) { + struct userdata *u; struct stat st; - const char *p; - int fd = -1; pa_sample_spec ss; pa_channel_map map; - pa_modargs *ma = NULL; + pa_modargs *ma; char *t; + struct pollfd *pollfd; - assert(c && m); + pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { - pa_log("failed to parse module arguments"); + pa_log("Failed to parse module arguments."); goto fail; } - ss = c->default_sample_spec; + ss = m->core->default_sample_spec; if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) { - pa_log("invalid sample format specification"); + pa_log("Invalid sample format specification or channel map"); goto fail; } - mkfifo(p = pa_modargs_get_value(ma, "file", DEFAULT_FIFO_NAME), 0777); + u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + m->userdata = u; + pa_memchunk_reset(&u->memchunk); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop); + u->rtpoll = pa_rtpoll_new(); + pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq); - if ((fd = open(p, O_RDWR)) < 0) { - pa_log("open('%s'): %s", p, pa_cstrerror(errno)); + u->filename = pa_xstrdup(pa_modargs_get_value(ma, "file", DEFAULT_FILE_NAME)); + + mkfifo(u->filename, 0666); + if ((u->fd = open(u->filename, O_RDWR|O_NOCTTY)) < 0) { + pa_log("open('%s'): %s", u->filename, pa_cstrerror(errno)); goto fail; } - pa_fd_set_cloexec(fd, 1); + pa_make_fd_cloexec(u->fd); + pa_make_fd_nonblock(u->fd); - if (fstat(fd, &st) < 0) { - pa_log("fstat('%s'): %s", p, pa_cstrerror(errno)); + if (fstat(u->fd, &st) < 0) { + pa_log("fstat('%s'): %s", u->filename, pa_cstrerror(errno)); goto fail; } if (!S_ISFIFO(st.st_mode)) { - pa_log("'%s' is not a FIFO.", p); + pa_log("'%s' is not a FIFO.", u->filename); goto fail; } - u = pa_xmalloc0(sizeof(struct userdata)); - u->filename = pa_xstrdup(p); - u->core = c; - u->module = m; - m->userdata = u; - - if (!(u->sink = pa_sink_new(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, &map))) { - pa_log("failed to create sink."); + if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, &map))) { + pa_log("Failed to create sink."); goto fail; } - u->sink->notify = notify_cb; - u->sink->get_latency = get_latency_cb; + + u->sink->parent.process_msg = sink_process_msg; u->sink->userdata = u; - pa_sink_set_owner(u->sink, m); - pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Unix FIFO sink '%s'", p)); + u->sink->flags = PA_SINK_LATENCY; + + pa_sink_set_module(u->sink, m); + pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); + pa_sink_set_rtpoll(u->sink, u->rtpoll); + pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Unix FIFO sink '%s'", u->filename)); pa_xfree(t); - u->io = pa_iochannel_new(c->mainloop, -1, fd); - assert(u->io); - pa_iochannel_set_callback(u->io, io_callback, u); + u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1); + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + pollfd->fd = u->fd; + pollfd->events = pollfd->revents = 0; - u->memchunk.memblock = NULL; - u->memchunk.length = 0; + if (!(u->thread = pa_thread_new(thread_func, u))) { + pa_log("Failed to create thread."); + goto fail; + } - u->defer_event = c->mainloop->defer_new(c->mainloop, defer_callback, u); - assert(u->defer_event); - c->mainloop->defer_enable(u->defer_event, 0); + pa_sink_put(u->sink); pa_modargs_free(ma); @@ -220,32 +285,48 @@ fail: if (ma) pa_modargs_free(ma); - if (fd >= 0) - close(fd); - - pa__done(c, m); + pa__done(m); return -1; } -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module*m) { struct userdata *u; - assert(c && m); + + pa_assert(m); if (!(u = m->userdata)) return; + if (u->sink) + pa_sink_unlink(u->sink); + + if (u->thread) { + pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); + pa_thread_free(u->thread); + } + + pa_thread_mq_done(&u->thread_mq); + + if (u->sink) + pa_sink_unref(u->sink); + if (u->memchunk.memblock) - pa_memblock_unref(u->memchunk.memblock); + pa_memblock_unref(u->memchunk.memblock); - pa_sink_disconnect(u->sink); - pa_sink_unref(u->sink); - pa_iochannel_free(u->io); - u->core->mainloop->defer_free(u->defer_event); + if (u->rtpoll_item) + pa_rtpoll_item_free(u->rtpoll_item); + + if (u->rtpoll) + pa_rtpoll_free(u->rtpoll); + + if (u->filename) { + unlink(u->filename); + pa_xfree(u->filename); + } - assert(u->filename); - unlink(u->filename); - pa_xfree(u->filename); + if (u->fd >= 0) + pa_assert_se(pa_close(u->fd) == 0); pa_xfree(u); } diff --git a/src/modules/module-pipe-source.c b/src/modules/module-pipe-source.c index 56c721b0..45708c68 100644 --- a/src/modules/module-pipe-source.c +++ b/src/modules/module-pipe-source.c @@ -28,22 +28,24 @@ #include <stdlib.h> #include <sys/stat.h> #include <stdio.h> -#include <assert.h> #include <errno.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <limits.h> +#include <sys/poll.h> #include <pulse/xmalloc.h> #include <pulsecore/core-error.h> -#include <pulsecore/iochannel.h> #include <pulsecore/source.h> #include <pulsecore/module.h> #include <pulsecore/core-util.h> #include <pulsecore/modargs.h> #include <pulsecore/log.h> +#include <pulsecore/thread.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/rtpoll.h> #include "module-pipe-source-symdef.h" @@ -58,18 +60,24 @@ PA_MODULE_USAGE( "rate=<sample rate> " "channel_map=<channel map>") -#define DEFAULT_FIFO_NAME "/tmp/music.input" +#define DEFAULT_FILE_NAME "/tmp/music.input" #define DEFAULT_SOURCE_NAME "fifo_input" struct userdata { pa_core *core; + pa_module *module; + pa_source *source; + + pa_thread *thread; + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; char *filename; + int fd; - pa_source *source; - pa_iochannel *io; - pa_module *module; - pa_memchunk chunk; + pa_memchunk memchunk; + + pa_rtpoll_item *rtpoll_item; }; static const char* const valid_modargs[] = { @@ -82,109 +90,168 @@ static const char* const valid_modargs[] = { NULL }; -static void do_read(struct userdata *u) { - ssize_t r; - pa_memchunk chunk; - assert(u); +static void thread_func(void *userdata) { + struct userdata *u = userdata; + int read_type = 0; - if (!pa_iochannel_is_readable(u->io)) - return; + pa_assert(u); - pa_module_set_used(u->module, pa_idxset_size(u->source->outputs)); + pa_log_debug("Thread starting up"); - if (!u->chunk.memblock) { - u->chunk.memblock = pa_memblock_new(u->core->mempool, PIPE_BUF); - u->chunk.index = chunk.length = 0; - } + pa_thread_mq_install(&u->thread_mq); + pa_rtpoll_install(u->rtpoll); - assert(u->chunk.memblock && u->chunk.memblock->length > u->chunk.index); - if ((r = pa_iochannel_read(u->io, (uint8_t*) u->chunk.memblock->data + u->chunk.index, u->chunk.memblock->length - u->chunk.index)) <= 0) { - pa_log("read(): %s", pa_cstrerror(errno)); - return; - } + for (;;) { + int ret; + struct pollfd *pollfd; + + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + + /* Try to read some data and pass it on to the source driver */ + if (u->source->thread_info.state == PA_SOURCE_RUNNING && pollfd->revents) { + ssize_t l; + void *p; - u->chunk.length = r; - pa_source_post(u->source, &u->chunk); - u->chunk.index += r; + if (!u->memchunk.memblock) { + u->memchunk.memblock = pa_memblock_new(u->core->mempool, PIPE_BUF); + u->memchunk.index = u->memchunk.length = 0; + } - if (u->chunk.index >= u->chunk.memblock->length) { - u->chunk.index = u->chunk.length = 0; - pa_memblock_unref(u->chunk.memblock); - u->chunk.memblock = NULL; + pa_assert(pa_memblock_get_length(u->memchunk.memblock) > u->memchunk.index); + + p = pa_memblock_acquire(u->memchunk.memblock); + l = pa_read(u->fd, (uint8_t*) p + u->memchunk.index, pa_memblock_get_length(u->memchunk.memblock) - u->memchunk.index, &read_type); + pa_memblock_release(u->memchunk.memblock); + + pa_assert(l != 0); /* EOF cannot happen, since we opened the fifo for both reading and writing */ + + if (l < 0) { + + if (errno == EINTR) + continue; + else if (errno != EAGAIN) { + pa_log("Faile to read data from FIFO: %s", pa_cstrerror(errno)); + goto fail; + } + + } else { + + u->memchunk.length = l; + pa_source_post(u->source, &u->memchunk); + u->memchunk.index += l; + + if (u->memchunk.index >= pa_memblock_get_length(u->memchunk.memblock)) { + pa_memblock_unref(u->memchunk.memblock); + pa_memchunk_reset(&u->memchunk); + } + + pollfd->revents = 0; + } + } + + /* Hmm, nothing to do. Let's sleep */ + pollfd->events = u->source->thread_info.state == PA_SOURCE_RUNNING ? POLLIN : 0; + + if ((ret = pa_rtpoll_run(u->rtpoll, 1)) < 0) + goto fail; + + if (ret == 0) + goto finish; + + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + if (pollfd->revents & ~POLLIN) { + pa_log("FIFO shutdown."); + goto fail; + } } -} -static void io_callback(PA_GCC_UNUSED pa_iochannel *io, void*userdata) { - struct userdata *u = userdata; - assert(u); - do_read(u); +fail: + /* If this was no regular exit from the loop we have to continue + * processing messages until we received PA_MESSAGE_SHUTDOWN */ + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + +finish: + pa_log_debug("Thread shutting down"); } -int pa__init(pa_core *c, pa_module*m) { - struct userdata *u = NULL; +int pa__init(pa_module*m) { + struct userdata *u; struct stat st; - const char *p; - int fd = -1; pa_sample_spec ss; pa_channel_map map; - pa_modargs *ma = NULL; + pa_modargs *ma; char *t; + struct pollfd *pollfd; - assert(c && m); + pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { - pa_log("failed to parse module arguments"); + pa_log("failed to parse module arguments."); goto fail; } - ss = c->default_sample_spec; + ss = m->core->default_sample_spec; if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) { pa_log("invalid sample format specification or channel map"); goto fail; } - mkfifo(p = pa_modargs_get_value(ma, "file", DEFAULT_FIFO_NAME), 0777); + u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + m->userdata = u; + pa_memchunk_reset(&u->memchunk); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop); + u->rtpoll = pa_rtpoll_new(); + pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq); + + u->filename = pa_xstrdup(pa_modargs_get_value(ma, "file", DEFAULT_FILE_NAME)); - if ((fd = open(p, O_RDWR)) < 0) { - pa_log("open('%s'): %s", p, pa_cstrerror(errno)); + mkfifo(u->filename, 0666); + if ((u->fd = open(u->filename, O_RDWR|O_NOCTTY)) < 0) { + pa_log("open('%s'): %s", u->filename, pa_cstrerror(errno)); goto fail; } - pa_fd_set_cloexec(fd, 1); + pa_make_fd_cloexec(u->fd); + pa_make_fd_nonblock(u->fd); - if (fstat(fd, &st) < 0) { - pa_log("fstat('%s'): %s", p, pa_cstrerror(errno)); + if (fstat(u->fd, &st) < 0) { + pa_log("fstat('%s'): %s",u->filename, pa_cstrerror(errno)); goto fail; } if (!S_ISFIFO(st.st_mode)) { - pa_log("'%s' is not a FIFO.", p); + pa_log("'%s' is not a FIFO.", u->filename); goto fail; } - u = pa_xmalloc0(sizeof(struct userdata)); - - u->filename = pa_xstrdup(p); - u->core = c; - - if (!(u->source = pa_source_new(c, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, &map))) { - pa_log("failed to create source."); + if (!(u->source = pa_source_new(m->core, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, &map))) { + pa_log("Failed to create source."); goto fail; } + u->source->userdata = u; - pa_source_set_owner(u->source, m); - pa_source_set_description(u->source, t = pa_sprintf_malloc("Unix FIFO source '%s'", p)); + u->source->flags = 0; + + pa_source_set_module(u->source, m); + pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); + pa_source_set_rtpoll(u->source, u->rtpoll); + pa_source_set_description(u->source, t = pa_sprintf_malloc("Unix FIFO source '%s'", u->filename)); pa_xfree(t); - u->io = pa_iochannel_new(c->mainloop, fd, -1); - assert(u->io); - pa_iochannel_set_callback(u->io, io_callback, u); + u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1); + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + pollfd->fd = u->fd; + pollfd->events = pollfd->revents = 0; - u->chunk.memblock = NULL; - u->chunk.index = u->chunk.length = 0; + if (!(u->thread = pa_thread_new(thread_func, u))) { + pa_log("Failed to create thread."); + goto fail; + } - u->module = m; - m->userdata = u; + pa_source_put(u->source); pa_modargs_free(ma); @@ -194,31 +261,48 @@ fail: if (ma) pa_modargs_free(ma); - if (fd >= 0) - close(fd); - - pa__done(c, m); + pa__done(m); return -1; } -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module*m) { struct userdata *u; - assert(c && m); + + pa_assert(m); if (!(u = m->userdata)) return; - if (u->chunk.memblock) - pa_memblock_unref(u->chunk.memblock); + if (u->source) + pa_source_unlink(u->source); + + if (u->thread) { + pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); + pa_thread_free(u->thread); + } + + pa_thread_mq_done(&u->thread_mq); + + if (u->source) + pa_source_unref(u->source); + + if (u->memchunk.memblock) + pa_memblock_unref(u->memchunk.memblock); - pa_source_disconnect(u->source); - pa_source_unref(u->source); - pa_iochannel_free(u->io); + if (u->rtpoll_item) + pa_rtpoll_item_free(u->rtpoll_item); + + if (u->rtpoll) + pa_rtpoll_free(u->rtpoll); + + if (u->filename) { + unlink(u->filename); + pa_xfree(u->filename); + } - assert(u->filename); - unlink(u->filename); - pa_xfree(u->filename); + if (u->fd >= 0) + pa_assert_se(pa_close(u->fd) == 0); pa_xfree(u); } diff --git a/src/modules/module-protocol-stub.c b/src/modules/module-protocol-stub.c index 5c8733fb..6bd78079 100644 --- a/src/modules/module-protocol-stub.c +++ b/src/modules/module-protocol-stub.c @@ -29,7 +29,6 @@ #include <string.h> #include <errno.h> #include <stdio.h> -#include <assert.h> #include <unistd.h> #include <limits.h> @@ -43,10 +42,9 @@ #include <netinet/in.h> #endif -#include "../pulsecore/winsock.h" - #include <pulse/xmalloc.h> +#include <pulsecore/winsock.h> #include <pulsecore/core-error.h> #include <pulsecore/module.h> #include <pulsecore/socket-server.h> @@ -154,7 +152,6 @@ #define protocol_free pa_protocol_esound_free #define TCPWRAP_SERVICE "esound" #define IPV4_PORT ESD_DEFAULT_PORT - #define UNIX_SOCKET ESD_UNIX_SOCKET_NAME #define MODULE_ARGUMENTS_COMMON "sink", "source", "auth-anonymous", "cookie", #ifdef USE_TCP_SOCKETS #include "module-esound-protocol-tcp-symdef.h" @@ -205,10 +202,9 @@ struct userdata { #endif }; -int pa__init(pa_core *c, pa_module*m) { +int pa__init(pa_module*m) { pa_modargs *ma = NULL; int ret = -1; - struct userdata *u = NULL; #if defined(USE_TCP_SOCKETS) @@ -219,9 +215,13 @@ int pa__init(pa_core *c, pa_module*m) { pa_socket_server *s; int r; char tmp[PATH_MAX]; + +#if defined(USE_PROTOCOL_ESOUND) + char tmp2[PATH_MAX]; +#endif #endif - assert(c && m); + pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { pa_log("Failed to parse module arguments"); @@ -239,22 +239,22 @@ int pa__init(pa_core *c, pa_module*m) { listen_on = pa_modargs_get_value(ma, "listen", NULL); if (listen_on) { - s_ipv6 = pa_socket_server_new_ipv6_string(c->mainloop, listen_on, port, TCPWRAP_SERVICE); - s_ipv4 = pa_socket_server_new_ipv4_string(c->mainloop, listen_on, port, TCPWRAP_SERVICE); + s_ipv6 = pa_socket_server_new_ipv6_string(m->core->mainloop, listen_on, port, TCPWRAP_SERVICE); + s_ipv4 = pa_socket_server_new_ipv4_string(m->core->mainloop, listen_on, port, TCPWRAP_SERVICE); } else { - s_ipv6 = pa_socket_server_new_ipv6_any(c->mainloop, port, TCPWRAP_SERVICE); - s_ipv4 = pa_socket_server_new_ipv4_any(c->mainloop, port, TCPWRAP_SERVICE); + s_ipv6 = pa_socket_server_new_ipv6_any(m->core->mainloop, port, TCPWRAP_SERVICE); + s_ipv4 = pa_socket_server_new_ipv4_any(m->core->mainloop, port, TCPWRAP_SERVICE); } if (!s_ipv4 && !s_ipv6) goto fail; if (s_ipv4) - if (!(u->protocol_ipv4 = protocol_new(c, s_ipv4, m, ma))) + if (!(u->protocol_ipv4 = protocol_new(m->core, s_ipv4, m, ma))) pa_socket_server_unref(s_ipv4); if (s_ipv6) - if (!(u->protocol_ipv6 = protocol_new(c, s_ipv6, m, ma))) + if (!(u->protocol_ipv6 = protocol_new(m->core, s_ipv6, m, ma))) pa_socket_server_unref(s_ipv6); if (!u->protocol_ipv4 && !u->protocol_ipv6) @@ -262,18 +262,23 @@ int pa__init(pa_core *c, pa_module*m) { #else - pa_runtime_path(pa_modargs_get_value(ma, "socket", UNIX_SOCKET), tmp, sizeof(tmp)); - u->socket_path = pa_xstrdup(tmp); - #if defined(USE_PROTOCOL_ESOUND) + snprintf(tmp2, sizeof(tmp2), "/tmp/.esd-%lu/socket", (unsigned long) getuid()); + pa_runtime_path(pa_modargs_get_value(ma, "socket", tmp2), tmp, sizeof(tmp)); + u->socket_path = pa_xstrdup(tmp); + /* This socket doesn't reside in our own runtime dir but in * /tmp/.esd/, hence we have to create the dir first */ - if (pa_make_secure_parent_dir(u->socket_path, c->is_system_instance ? 0755 : 0700, (uid_t)-1, (gid_t)-1) < 0) { + if (pa_make_secure_parent_dir(u->socket_path, m->core->is_system_instance ? 0755 : 0700, (uid_t)-1, (gid_t)-1) < 0) { pa_log("Failed to create socket directory '%s': %s\n", u->socket_path, pa_cstrerror(errno)); goto fail; } + +#else + pa_runtime_path(pa_modargs_get_value(ma, "socket", UNIX_SOCKET), tmp, sizeof(tmp)); + u->socket_path = pa_xstrdup(tmp); #endif if ((r = pa_unix_socket_remove_stale(tmp)) < 0) { @@ -284,10 +289,10 @@ int pa__init(pa_core *c, pa_module*m) { if (r) pa_log("Removed stale UNIX socket '%s'.", tmp); - if (!(s = pa_socket_server_new_unix(c->mainloop, tmp))) + if (!(s = pa_socket_server_new_unix(m->core->mainloop, tmp))) goto fail; - if (!(u->protocol_unix = protocol_new(c, s, m, ma))) + if (!(u->protocol_unix = protocol_new(m->core, s, m, ma))) goto fail; #endif @@ -333,11 +338,10 @@ fail: goto finish; } -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module*m) { struct userdata *u; - assert(c); - assert(m); + pa_assert(m); u = m->userdata; @@ -358,7 +362,6 @@ void pa__done(pa_core *c, pa_module*m) { } #endif - pa_xfree(u->socket_path); #endif diff --git a/src/modules/module-remap-sink.c b/src/modules/module-remap-sink.c new file mode 100644 index 00000000..e863c0c3 --- /dev/null +++ b/src/modules/module-remap-sink.c @@ -0,0 +1,334 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/namereg.h> +#include <pulsecore/sink.h> +#include <pulsecore/module.h> +#include <pulsecore/core-util.h> +#include <pulsecore/modargs.h> +#include <pulsecore/log.h> +#include <pulsecore/thread.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/rtpoll.h> + +#include "module-remap-sink-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering") +PA_MODULE_DESCRIPTION("Virtual channel remapping sink") +PA_MODULE_VERSION(PACKAGE_VERSION) +PA_MODULE_USAGE( + "sink_name=<name for the sink> " + "master=<name of sink to remap> " + "master_channel_map=<channel map> " + "format=<sample format> " + "channels=<number of channels> " + "rate=<sample rate> " + "channel_map=<channel map>") + +struct userdata { + pa_core *core; + pa_module *module; + + pa_sink *sink, *master; + pa_sink_input *sink_input; + + pa_memchunk memchunk; +}; + +static const char* const valid_modargs[] = { + "sink_name", + "master", + "master_channel_map", + "rate", + "format", + "channels", + "channel_map", + NULL +}; + +/* Called from I/O thread context */ +static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SINK(o)->userdata; + + switch (code) { + + case PA_SINK_MESSAGE_GET_LATENCY: { + pa_usec_t usec = 0; + + if (PA_MSGOBJECT(u->master)->process_msg(PA_MSGOBJECT(u->master), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0) + usec = 0; + + *((pa_usec_t*) data) = usec + pa_bytes_to_usec(u->memchunk.length, &u->sink->sample_spec); + return 0; + } + } + + return pa_sink_process_msg(o, code, data, offset, chunk); +} + +/* Called from main context */ +static int sink_set_state(pa_sink *s, pa_sink_state_t state) { + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (PA_SINK_LINKED(state) && u->sink_input && PA_SINK_INPUT_LINKED(pa_sink_input_get_state(u->sink_input))) + pa_sink_input_cork(u->sink_input, state == PA_SINK_SUSPENDED); + + return 0; +} + +/* Called from I/O thread context */ +static int sink_input_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SINK_INPUT(o)->userdata; + + switch (code) { + case PA_SINK_INPUT_MESSAGE_GET_LATENCY: + *((pa_usec_t*) data) = pa_bytes_to_usec(u->memchunk.length, &u->sink_input->sample_spec); + + /* Fall through, the default handler will add in the extra + * latency added by the resampler */ + break; + } + + return pa_sink_input_process_msg(o, code, data, offset, chunk); +} + +/* Called from I/O thread context */ +static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + if (!u->memchunk.memblock) + pa_sink_render(u->sink, length, &u->memchunk); + + pa_assert(u->memchunk.memblock); + *chunk = u->memchunk; + pa_memblock_ref(chunk->memblock); + return 0; +} + +/* Called from I/O thread context */ +static void sink_input_drop_cb(pa_sink_input *i, size_t length) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + pa_assert(length > 0); + + if (u->memchunk.memblock) { + + if (length < u->memchunk.length) { + u->memchunk.index += length; + u->memchunk.length -= length; + return; + } + + pa_memblock_unref(u->memchunk.memblock); + length -= u->memchunk.length; + pa_memchunk_reset(&u->memchunk); + } + + if (length > 0) + pa_sink_skip(u->sink, length); +} + +/* Called from I/O thread context */ +static void sink_input_detach_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_sink_detach_within_thread(u->sink); +} + +/* Called from I/O thread context */ +static void sink_input_attach_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_sink_set_asyncmsgq(u->sink, i->sink->asyncmsgq); + pa_sink_set_rtpoll(u->sink, i->sink->rtpoll); + + pa_sink_attach_within_thread(u->sink); +} + +/* Called from main context */ +static void sink_input_kill_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_sink_input_unlink(u->sink_input); + pa_sink_input_unref(u->sink_input); + u->sink_input = NULL; + + pa_sink_unlink(u->sink); + pa_sink_unref(u->sink); + u->sink = NULL; + + pa_module_unload_request(u->module); +} + +int pa__init(pa_module*m) { + struct userdata *u; + pa_sample_spec ss; + pa_channel_map sink_map, stream_map; + pa_modargs *ma; + char *t; + pa_sink *master; + pa_sink_input_new_data data; + char *default_sink_name = NULL; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments."); + goto fail; + } + + if (!(master = pa_namereg_get(m->core, pa_modargs_get_value(ma, "master", NULL), PA_NAMEREG_SINK, 1))) { + pa_log("Master sink not found"); + goto fail; + } + + ss = master->sample_spec; + sink_map = master->channel_map; + if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &sink_map, PA_CHANNEL_MAP_DEFAULT) < 0) { + pa_log("Invalid sample format specification or channel map"); + goto fail; + } + + stream_map = sink_map; + if (pa_modargs_get_channel_map(ma, "master_channel_map", &stream_map) < 0) { + pa_log("Invalid master hannel map"); + goto fail; + } + + if (stream_map.channels != ss.channels) { + pa_log("Number of channels doesn't match"); + goto fail; + } + + u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + m->userdata = u; + u->master = master; + pa_memchunk_reset(&u->memchunk); + + default_sink_name = pa_sprintf_malloc("%s.remapped", master->name); + + /* Create sink */ + if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", default_sink_name), 0, &ss, &sink_map))) { + pa_log("Failed to create sink."); + goto fail; + } + + u->sink->parent.process_msg = sink_process_msg; + u->sink->set_state = sink_set_state; + u->sink->userdata = u; + u->sink->flags = PA_SINK_LATENCY; + + pa_sink_set_module(u->sink, m); + pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Remapped %s", master->description)); + pa_xfree(t); + pa_sink_set_asyncmsgq(u->sink, master->asyncmsgq); + pa_sink_set_rtpoll(u->sink, master->rtpoll); + + /* Create sink input */ + pa_sink_input_new_data_init(&data); + data.sink = u->master; + data.driver = __FILE__; + data.name = "Remapped Stream"; + pa_sink_input_new_data_set_sample_spec(&data, &ss); + pa_sink_input_new_data_set_channel_map(&data, &stream_map); + data.module = m; + + if (!(u->sink_input = pa_sink_input_new(m->core, &data, PA_SINK_INPUT_DONT_MOVE))) + goto fail; + + u->sink_input->parent.process_msg = sink_input_process_msg; + u->sink_input->peek = sink_input_peek_cb; + u->sink_input->drop = sink_input_drop_cb; + u->sink_input->kill = sink_input_kill_cb; + u->sink_input->attach = sink_input_attach_cb; + u->sink_input->detach = sink_input_detach_cb; + u->sink_input->userdata = u; + + pa_sink_put(u->sink); + pa_sink_input_put(u->sink_input); + + pa_modargs_free(ma); + pa_xfree(default_sink_name); + + return 0; + +fail: + if (ma) + pa_modargs_free(ma); + + pa__done(m); + + pa_xfree(default_sink_name); + + return -1; +} + +void pa__done(pa_module*m) { + struct userdata *u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->sink_input) { + pa_sink_input_unlink(u->sink_input); + pa_sink_input_unref(u->sink_input); + } + + if (u->sink) { + pa_sink_unlink(u->sink); + pa_sink_unref(u->sink); + } + + if (u->memchunk.memblock) + pa_memblock_unref(u->memchunk.memblock); + + pa_xfree(u); +} diff --git a/src/modules/module-rescue-streams.c b/src/modules/module-rescue-streams.c index 25005f25..5cabef76 100644 --- a/src/modules/module-rescue-streams.c +++ b/src/modules/module-rescue-streams.c @@ -52,20 +52,26 @@ static pa_hook_result_t sink_hook_callback(pa_core *c, pa_sink *sink, void* user pa_sink_input *i; pa_sink *target; - assert(c); - assert(sink); + pa_assert(c); + pa_assert(sink); if (!pa_idxset_size(sink->inputs)) { pa_log_debug("No sink inputs to move away."); return PA_HOOK_OK; } - if (!(target = pa_namereg_get(c, NULL, PA_NAMEREG_SINK, 0))) { - pa_log_info("No evacuation sink found."); - return PA_HOOK_OK; - } + if (!(target = pa_namereg_get(c, NULL, PA_NAMEREG_SINK, 0)) || target == sink) { + uint32_t idx; + + for (target = pa_idxset_first(c->sinks, &idx); target; target = pa_idxset_next(c->sinks, &idx)) + if (target != sink) + break; - assert(target != sink); + if (!target) { + pa_log_info("No evacuation sink found."); + return PA_HOOK_OK; + } + } while ((i = pa_idxset_first(sink->inputs, NULL))) { if (pa_sink_input_move_to(i, target, 1) < 0) { @@ -84,20 +90,28 @@ static pa_hook_result_t source_hook_callback(pa_core *c, pa_source *source, void pa_source_output *o; pa_source *target; - assert(c); - assert(source); + pa_assert(c); + pa_assert(source); if (!pa_idxset_size(source->outputs)) { pa_log_debug("No source outputs to move away."); return PA_HOOK_OK; } - if (!(target = pa_namereg_get(c, NULL, PA_NAMEREG_SOURCE, 0))) { - pa_log_info("No evacuation source found."); - return PA_HOOK_OK; + if (!(target = pa_namereg_get(c, NULL, PA_NAMEREG_SOURCE, 0)) || target == source) { + uint32_t idx; + + for (target = pa_idxset_first(c->sources, &idx); target; target = pa_idxset_next(c->sources, &idx)) + if (target != source && !target->monitor_of == !source->monitor_of) + break; + + if (!target) { + pa_log_info("No evacuation source found."); + return PA_HOOK_OK; + } } - assert(target != source); + pa_assert(target != source); while ((o = pa_idxset_first(source->outputs, NULL))) { if (pa_source_output_move_to(o, target) < 0) { @@ -112,12 +126,11 @@ static pa_hook_result_t source_hook_callback(pa_core *c, pa_source *source, void return PA_HOOK_OK; } -int pa__init(pa_core *c, pa_module*m) { +int pa__init(pa_module*m) { pa_modargs *ma = NULL; struct userdata *u; - assert(c); - assert(m); + pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { pa_log("Failed to parse module arguments"); @@ -125,18 +138,17 @@ int pa__init(pa_core *c, pa_module*m) { } m->userdata = u = pa_xnew(struct userdata, 1); - u->sink_slot = pa_hook_connect(&c->hook_sink_disconnect, (pa_hook_cb_t) sink_hook_callback, NULL); - u->source_slot = pa_hook_connect(&c->hook_source_disconnect, (pa_hook_cb_t) source_hook_callback, NULL); + u->sink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], (pa_hook_cb_t) sink_hook_callback, NULL); + u->source_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], (pa_hook_cb_t) source_hook_callback, NULL); pa_modargs_free(ma); return 0; } -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module*m) { struct userdata *u; - assert(c); - assert(m); + pa_assert(m); if (!m->userdata) return; diff --git a/src/modules/module-sine.c b/src/modules/module-sine.c index 661455b3..65b8ee7f 100644 --- a/src/modules/module-sine.c +++ b/src/modules/module-sine.c @@ -26,7 +26,6 @@ #endif #include <stdio.h> -#include <assert.h> #include <math.h> #include <pulse/xmalloc.h> @@ -36,6 +35,7 @@ #include <pulsecore/modargs.h> #include <pulsecore/namereg.h> #include <pulsecore/log.h> +#include <pulsecore/core-util.h> #include "module-sine-symdef.h" @@ -58,36 +58,46 @@ static const char* const valid_modargs[] = { NULL, }; -static int sink_input_peek(pa_sink_input *i, pa_memchunk *chunk) { +static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) { struct userdata *u; - assert(i && chunk && i->userdata); + + pa_assert(i); u = i->userdata; + pa_assert(u); + pa_assert(chunk); chunk->memblock = pa_memblock_ref(u->memblock); chunk->index = u->peek_index; - chunk->length = u->memblock->length - u->peek_index; + chunk->length = pa_memblock_get_length(u->memblock) - u->peek_index; + return 0; } -static void sink_input_drop(pa_sink_input *i, const pa_memchunk *chunk, size_t length) { +static void sink_input_drop_cb(pa_sink_input *i, size_t length) { struct userdata *u; - assert(i && chunk && length && i->userdata); - u = i->userdata; + size_t l; - assert(chunk->memblock == u->memblock && length <= u->memblock->length-u->peek_index); + pa_assert(i); + u = i->userdata; + pa_assert(u); + pa_assert(length > 0); u->peek_index += length; - if (u->peek_index >= u->memblock->length) - u->peek_index = 0; + l = pa_memblock_get_length(u->memblock); + + while (u->peek_index >= l) + u->peek_index -= l; } -static void sink_input_kill(pa_sink_input *i) { +static void sink_input_kill_cb(pa_sink_input *i) { struct userdata *u; - assert(i && i->userdata); + + pa_assert(i); u = i->userdata; + pa_assert(u); - pa_sink_input_disconnect(u->sink_input); + pa_sink_input_unlink(u->sink_input); pa_sink_input_unref(u->sink_input); u->sink_input = NULL; @@ -103,14 +113,14 @@ static void calc_sine(float *f, size_t l, float freq) { f[i] = (float) sin((double) i/l*M_PI*2*freq)/2; } -int pa__init(pa_core *c, pa_module*m) { +int pa__init(pa_module*m) { pa_modargs *ma = NULL; struct userdata *u; pa_sink *sink; - const char *sink_name; pa_sample_spec ss; uint32_t frequency; char t[256]; + void *p; pa_sink_input_new_data data; if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { @@ -118,15 +128,14 @@ int pa__init(pa_core *c, pa_module*m) { goto fail; } - m->userdata = u = pa_xmalloc(sizeof(struct userdata)); - u->core = c; + m->userdata = u = pa_xnew0(struct userdata, 1); + u->core = m->core; u->module = m; u->sink_input = NULL; u->memblock = NULL; + u->peek_index = 0; - sink_name = pa_modargs_get_value(ma, "sink", NULL); - - if (!(sink = pa_namereg_get(c, sink_name, PA_NAMEREG_SINK, 1))) { + if (!(sink = pa_namereg_get(m->core, pa_modargs_get_value(ma, "sink", NULL), PA_NAMEREG_SINK, 1))) { pa_log("No such sink."); goto fail; } @@ -141,10 +150,12 @@ int pa__init(pa_core *c, pa_module*m) { goto fail; } - u->memblock = pa_memblock_new(c->mempool, pa_bytes_per_second(&ss)); - calc_sine(u->memblock->data, u->memblock->length, frequency); + u->memblock = pa_memblock_new(m->core->mempool, pa_bytes_per_second(&ss)); + p = pa_memblock_acquire(u->memblock); + calc_sine(p, pa_memblock_get_length(u->memblock), frequency); + pa_memblock_release(u->memblock); - snprintf(t, sizeof(t), "Sine Generator at %u Hz", frequency); + pa_snprintf(t, sizeof(t), "Sine Generator at %u Hz", frequency); pa_sink_input_new_data_init(&data); data.sink = sink; @@ -153,15 +164,15 @@ int pa__init(pa_core *c, pa_module*m) { pa_sink_input_new_data_set_sample_spec(&data, &ss); data.module = m; - if (!(u->sink_input = pa_sink_input_new(c, &data, 0))) + if (!(u->sink_input = pa_sink_input_new(m->core, &data, 0))) goto fail; - u->sink_input->peek = sink_input_peek; - u->sink_input->drop = sink_input_drop; - u->sink_input->kill = sink_input_kill; + u->sink_input->peek = sink_input_peek_cb; + u->sink_input->drop = sink_input_drop_cb; + u->sink_input->kill = sink_input_kill_cb; u->sink_input->userdata = u; - u->peek_index = 0; + pa_sink_input_put(u->sink_input); pa_modargs_free(ma); return 0; @@ -170,24 +181,26 @@ fail: if (ma) pa_modargs_free(ma); - pa__done(c, m); + pa__done(m); return -1; } -void pa__done(pa_core *c, pa_module*m) { - struct userdata *u = m->userdata; - assert(c && m); +void pa__done(pa_module*m) { + struct userdata *u; + + pa_assert(m); - if (!u) + if (!(u = m->userdata)) return; if (u->sink_input) { - pa_sink_input_disconnect(u->sink_input); + pa_sink_input_unlink(u->sink_input); pa_sink_input_unref(u->sink_input); } if (u->memblock) pa_memblock_unref(u->memblock); + pa_xfree(u); } diff --git a/src/modules/module-solaris.c b/src/modules/module-solaris.c index a50f1ecf..a8a94712 100644 --- a/src/modules/module-solaris.c +++ b/src/modules/module-solaris.c @@ -4,7 +4,7 @@ This file is part of PulseAudio. Copyright 2006 Lennart Poettering - Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + Copyright 2006-2007 Pierre Ossman <ossman@cendio.se> for Cendio AB PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published @@ -57,6 +57,9 @@ #include <pulsecore/modargs.h> #include <pulsecore/log.h> #include <pulsecore/core-error.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/rtpoll.h> +#include <pulsecore/thread.h> #include "module-solaris-symdef.h" @@ -75,12 +78,14 @@ PA_MODULE_USAGE( "channel_map=<channel map>") struct userdata { + pa_core *core; pa_sink *sink; pa_source *source; - pa_iochannel *io; - pa_core *core; - pa_time_event *timer; - pa_usec_t poll_timeout; + + pa_thread *thread; + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; + pa_signal_event *sig; pa_memchunk memchunk; @@ -90,9 +95,9 @@ struct userdata { uint32_t frame_size; uint32_t buffer_size; unsigned int written_bytes, read_bytes; - int sink_underflow; int fd; + pa_rtpoll_item *rtpoll_item; pa_module *module; }; @@ -114,309 +119,357 @@ static const char* const valid_modargs[] = { #define DEFAULT_SOURCE_NAME "solaris_input" #define DEFAULT_DEVICE "/dev/audio" -#define CHUNK_SIZE 2048 - -static void update_usage(struct userdata *u) { - pa_module_set_used(u->module, - (u->sink ? pa_sink_used_by(u->sink) : 0) + - (u->source ? pa_source_used_by(u->source) : 0)); -} - -static void do_write(struct userdata *u) { - audio_info_t info; +static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SINK(o)->userdata; int err; - size_t len; - ssize_t r; + audio_info_t info; - assert(u); + switch (code) { + case PA_SINK_MESSAGE_GET_LATENCY: { + pa_usec_t r = 0; - /* We cannot check pa_iochannel_is_writable() because of our buffer hack */ - if (!u->sink) - return; + if (u->fd >= 0) { - update_usage(u); + err = ioctl(u->fd, AUDIO_GETINFO, &info); + pa_assert(err >= 0); - err = ioctl(u->fd, AUDIO_GETINFO, &info); - assert(err >= 0); + r += pa_bytes_to_usec(u->written_bytes, &PA_SINK(o)->sample_spec); + r -= pa_bytes_to_usec(info.play.samples * u->frame_size, &PA_SINK(o)->sample_spec); - /* - * Since we cannot modify the size of the output buffer we fake it - * by not filling it more than u->buffer_size. - */ - len = u->buffer_size; - len -= u->written_bytes - (info.play.samples * u->frame_size); + if (u->memchunk.memblock) + r += pa_bytes_to_usec(u->memchunk.length, &PA_SINK(o)->sample_spec); + } - /* The sample counter can sometimes go backwards :( */ - if (len > u->buffer_size) - len = 0; + *((pa_usec_t*) data) = r; - if (!u->sink_underflow && (len == u->buffer_size)) - pa_log_debug("Solaris buffer underflow!"); + return 0; + } - len -= len % u->frame_size; + case PA_SINK_MESSAGE_SET_VOLUME: + if (u->fd >= 0) { + AUDIO_INITINFO(&info); + + info.play.gain = pa_cvolume_avg((pa_cvolume*)data) * AUDIO_MAX_GAIN / PA_VOLUME_NORM; + assert(info.play.gain <= AUDIO_MAX_GAIN); + + if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) { + if (errno == EINVAL) + pa_log("AUDIO_SETINFO: Unsupported volume."); + else + pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno)); + } else { + return 0; + } + } + break; - if (len == 0) - return; + case PA_SINK_MESSAGE_GET_VOLUME: + if (u->fd >= 0) { + err = ioctl(u->fd, AUDIO_GETINFO, &info); + assert(err >= 0); - if (!u->memchunk.length) { - if (pa_sink_render(u->sink, len, &u->memchunk) < 0) { - u->sink_underflow = 1; - return; - } - } + pa_cvolume_set((pa_cvolume*) data, ((pa_cvolume*) data)->channels, + info.play.gain * PA_VOLUME_NORM / AUDIO_MAX_GAIN); - u->sink_underflow = 0; + return 0; + } + break; - assert(u->memchunk.memblock); - assert(u->memchunk.memblock->data); - assert(u->memchunk.length); + case PA_SINK_MESSAGE_SET_MUTE: + if (u->fd >= 0) { + AUDIO_INITINFO(&info); - if (u->memchunk.length < len) { - len = u->memchunk.length; - len -= len % u->frame_size; - assert(len); - } + info.output_muted = !!PA_PTR_TO_UINT(data); - if ((r = pa_iochannel_write(u->io, - (uint8_t*) u->memchunk.memblock->data + u->memchunk.index, len)) < 0) { - pa_log("write() failed: %s", pa_cstrerror(errno)); - return; - } + if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) + pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno)); + else + return 0; + } + break; - assert(r % u->frame_size == 0); + case PA_SINK_MESSAGE_GET_MUTE: + if (u->fd >= 0) { + err = ioctl(u->fd, AUDIO_GETINFO, &info); + pa_assert(err >= 0); - u->memchunk.index += r; - u->memchunk.length -= r; + *(int*)data = !!info.output_muted; - if (u->memchunk.length <= 0) { - pa_memblock_unref(u->memchunk.memblock); - u->memchunk.memblock = NULL; + return 0; + } + break; } - u->written_bytes += r; + return pa_sink_process_msg(o, code, data, offset, chunk); } -static void do_read(struct userdata *u) { - pa_memchunk memchunk; +static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SOURCE(o)->userdata; int err; - size_t l; - ssize_t r; - assert(u); + audio_info_t info; - if (!u->source || !pa_iochannel_is_readable(u->io)) - return; + switch (code) { + case PA_SOURCE_MESSAGE_GET_LATENCY: { + pa_usec_t r = 0; - update_usage(u); + if (u->fd) { + err = ioctl(u->fd, AUDIO_GETINFO, &info); + pa_assert(err >= 0); - err = ioctl(u->fd, I_NREAD, &l); - assert(err >= 0); + r += pa_bytes_to_usec(info.record.samples * u->frame_size, &PA_SOURCE(o)->sample_spec); + r -= pa_bytes_to_usec(u->read_bytes, &PA_SOURCE(o)->sample_spec); + } - /* This is to make sure it fits in the memory pool. Also, a page - should be the most efficient transfer size. */ - if (l > u->page_size) - l = u->page_size; + *((pa_usec_t*) data) = r; - memchunk.memblock = pa_memblock_new(u->core->mempool, l); - assert(memchunk.memblock); - if ((r = pa_iochannel_read(u->io, memchunk.memblock->data, memchunk.memblock->length)) < 0) { - pa_memblock_unref(memchunk.memblock); - if (errno != EAGAIN) - pa_log("read() failed: %s", pa_cstrerror(errno)); - return; - } + return 0; + } - assert(r <= (ssize_t) memchunk.memblock->length); - memchunk.length = memchunk.memblock->length = r; - memchunk.index = 0; + case PA_SOURCE_MESSAGE_SET_VOLUME: + if (u->fd >= 0) { + AUDIO_INITINFO(&info); + + info.record.gain = pa_cvolume_avg((pa_cvolume*) data) * AUDIO_MAX_GAIN / PA_VOLUME_NORM; + assert(info.record.gain <= AUDIO_MAX_GAIN); + + if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) { + if (errno == EINVAL) + pa_log("AUDIO_SETINFO: Unsupported volume."); + else + pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno)); + } else { + return 0; + } + } + break; - pa_source_post(u->source, &memchunk); - pa_memblock_unref(memchunk.memblock); + case PA_SOURCE_MESSAGE_GET_VOLUME: + if (u->fd >= 0) { + err = ioctl(u->fd, AUDIO_GETINFO, &info); + pa_assert(err >= 0); - u->read_bytes += r; -} + pa_cvolume_set((pa_cvolume*) data, ((pa_cvolume*) data)->channels, + info.record.gain * PA_VOLUME_NORM / AUDIO_MAX_GAIN); -static void io_callback(pa_iochannel *io, void*userdata) { - struct userdata *u = userdata; - assert(u); - do_write(u); - do_read(u); -} + return 0; + } + break; + } -static void timer_cb(pa_mainloop_api*a, pa_time_event *e, const struct timeval *tv, void *userdata) { - struct userdata *u = userdata; - struct timeval ntv; + return pa_source_process_msg(o, code, data, offset, chunk); +} - assert(u); +static void clear_underflow(struct userdata *u) +{ + audio_info_t info; - do_write(u); + AUDIO_INITINFO(&info); - pa_gettimeofday(&ntv); - pa_timeval_add(&ntv, u->poll_timeout); + info.play.error = 0; - a->time_restart(e, &ntv); + if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) + pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno)); } -static void sig_callback(pa_mainloop_api *api, pa_signal_event*e, int sig, void *userdata) { - struct userdata *u = userdata; - pa_cvolume old_vol; +static void clear_overflow(struct userdata *u) +{ + audio_info_t info; - assert(u); + AUDIO_INITINFO(&info); - if (u->sink) { - assert(u->sink->get_hw_volume); - memcpy(&old_vol, &u->sink->hw_volume, sizeof(pa_cvolume)); - if (u->sink->get_hw_volume(u->sink) < 0) - return; - if (memcmp(&old_vol, &u->sink->hw_volume, sizeof(pa_cvolume)) != 0) { - pa_subscription_post(u->sink->core, - PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, - u->sink->index); - } - } + info.record.error = 0; - if (u->source) { - assert(u->source->get_hw_volume); - memcpy(&old_vol, &u->source->hw_volume, sizeof(pa_cvolume)); - if (u->source->get_hw_volume(u->source) < 0) - return; - if (memcmp(&old_vol, &u->source->hw_volume, sizeof(pa_cvolume)) != 0) { - pa_subscription_post(u->source->core, - PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, - u->source->index); - } - } + if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) + pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno)); } -static pa_usec_t sink_get_latency_cb(pa_sink *s) { - pa_usec_t r = 0; - audio_info_t info; - int err; - struct userdata *u = s->userdata; - assert(s && u && u->sink); +static void thread_func(void *userdata) { + struct userdata *u = userdata; + unsigned short revents = 0; + int ret; - err = ioctl(u->fd, AUDIO_GETINFO, &info); - assert(err >= 0); + pa_assert(u); - r += pa_bytes_to_usec(u->written_bytes, &s->sample_spec); - r -= pa_bytes_to_usec(info.play.samples * u->frame_size, &s->sample_spec); + pa_log_debug("Thread starting up"); - if (u->memchunk.memblock) - r += pa_bytes_to_usec(u->memchunk.length, &s->sample_spec); + if (u->core->high_priority) + pa_make_realtime(); - return r; -} + pa_thread_mq_install(&u->thread_mq); + pa_rtpoll_install(u->rtpoll); -static pa_usec_t source_get_latency_cb(pa_source *s) { - pa_usec_t r = 0; - struct userdata *u = s->userdata; - audio_info_t info; - int err; - assert(s && u && u->source); + for (;;) { + /* Render some data and write it to the dsp */ - err = ioctl(u->fd, AUDIO_GETINFO, &info); - assert(err >= 0); + if (u->sink && PA_SINK_OPENED(u->sink->thread_info.state)) { + audio_info_t info; + int err; + size_t len; - r += pa_bytes_to_usec(info.record.samples * u->frame_size, &s->sample_spec); - r -= pa_bytes_to_usec(u->read_bytes, &s->sample_spec); + err = ioctl(u->fd, AUDIO_GETINFO, &info); + pa_assert(err >= 0); - return r; -} + /* + * Since we cannot modify the size of the output buffer we fake it + * by not filling it more than u->buffer_size. + */ + len = u->buffer_size; + len -= u->written_bytes - (info.play.samples * u->frame_size); -static int sink_get_hw_volume_cb(pa_sink *s) { - struct userdata *u = s->userdata; - audio_info_t info; - int err; + /* The sample counter can sometimes go backwards :( */ + if (len > u->buffer_size) + len = 0; - err = ioctl(u->fd, AUDIO_GETINFO, &info); - assert(err >= 0); + if (info.play.error) { + pa_log_debug("Solaris buffer underflow!"); + clear_underflow(u); + } - pa_cvolume_set(&s->hw_volume, s->hw_volume.channels, - info.play.gain * PA_VOLUME_NORM / AUDIO_MAX_GAIN); + len -= len % u->frame_size; - return 0; -} + while (len) { + void *p; + ssize_t r; -static int sink_set_hw_volume_cb(pa_sink *s) { - struct userdata *u = s->userdata; - audio_info_t info; + if (!u->memchunk.length) + pa_sink_render(u->sink, len, &u->memchunk); - AUDIO_INITINFO(&info); + pa_assert(u->memchunk.length); - info.play.gain = pa_cvolume_avg(&s->hw_volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM; - assert(info.play.gain <= AUDIO_MAX_GAIN); + p = pa_memblock_acquire(u->memchunk.memblock); + r = pa_write(u->fd, (uint8_t*) p + u->memchunk.index, u->memchunk.length, NULL); + pa_memblock_release(u->memchunk.memblock); - if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) { - if (errno == EINVAL) - pa_log("AUDIO_SETINFO: Unsupported volume."); - else - pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno)); - return -1; - } + if (r < 0) { + if (errno == EINTR) + continue; + else if (errno != EAGAIN) { + pa_log("Failed to read data from DSP: %s", pa_cstrerror(errno)); + goto fail; + } + } else { + pa_assert(r % u->frame_size == 0); - return 0; -} + u->memchunk.index += r; + u->memchunk.length -= r; -static int sink_get_hw_mute_cb(pa_sink *s) { - struct userdata *u = s->userdata; - audio_info_t info; - int err; + if (u->memchunk.length <= 0) { + pa_memblock_unref(u->memchunk.memblock); + pa_memchunk_reset(&u->memchunk); + } - err = ioctl(u->fd, AUDIO_GETINFO, &info); - assert(err >= 0); + len -= r; + u->written_bytes += r; + } + } + } - s->hw_muted = !!info.output_muted; + /* Try to read some data and pass it on to the source driver */ + + if (u->source && PA_SOURCE_OPENED(u->source->thread_info.state) && ((revents & POLLIN))) { + pa_memchunk memchunk; + int err; + size_t l; + void *p; + ssize_t r; + audio_info_t info; + + err = ioctl(u->fd, AUDIO_GETINFO, &info); + pa_assert(err >= 0); + + if (info.record.error) { + pa_log_debug("Solaris buffer overflow!"); + clear_overflow(u); + } + + err = ioctl(u->fd, I_NREAD, &l); + pa_assert(err >= 0); + + if (l > 0) { + /* This is to make sure it fits in the memory pool. Also, a page + should be the most efficient transfer size. */ + if (l > u->page_size) + l = u->page_size; + + memchunk.memblock = pa_memblock_new(u->core->mempool, l); + pa_assert(memchunk.memblock); + + p = pa_memblock_acquire(memchunk.memblock); + r = pa_read(u->fd, p, l, NULL); + pa_memblock_release(memchunk.memblock); + + if (r < 0) { + pa_memblock_unref(memchunk.memblock); + if (errno != EAGAIN) { + pa_log("Failed to read data from DSP: %s", pa_cstrerror(errno)); + goto fail; + } + } else { + memchunk.index = 0; + memchunk.length = r; + + pa_source_post(u->source, &memchunk); + pa_memblock_unref(memchunk.memblock); + + u->read_bytes += r; + + revents &= ~POLLIN; + } + } + } - return 0; -} + if (u->fd >= 0) { + struct pollfd *pollfd; -static int sink_set_hw_mute_cb(pa_sink *s) { - struct userdata *u = s->userdata; - audio_info_t info; + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + pollfd->events = + ((u->source && PA_SOURCE_OPENED(u->source->thread_info.state)) ? POLLIN : 0); + } - AUDIO_INITINFO(&info); + /* Hmm, nothing to do. Let's sleep */ + if ((ret = pa_rtpoll_run(u->rtpoll, 1)) < 0) + goto fail; - info.output_muted = !!s->hw_muted; + if (ret == 0) + goto finish; - if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) { - pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno)); - return -1; - } + if (u->fd >= 0) { + struct pollfd *pollfd; - return 0; -} + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); -static int source_get_hw_volume_cb(pa_source *s) { - struct userdata *u = s->userdata; - audio_info_t info; - int err; + if (pollfd->revents & ~(POLLOUT|POLLIN)) { + pa_log("DSP shutdown."); + goto fail; + } - err = ioctl(u->fd, AUDIO_GETINFO, &info); - assert(err >= 0); + revents = pollfd->revents; + } else + revents = 0; + } - pa_cvolume_set(&s->hw_volume, s->hw_volume.channels, - info.record.gain * PA_VOLUME_NORM / AUDIO_MAX_GAIN); +fail: + /* We have to continue processing messages until we receive the + * SHUTDOWN message */ + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); - return 0; +finish: + pa_log_debug("Thread shutting down"); } -static int source_set_hw_volume_cb(pa_source *s) { - struct userdata *u = s->userdata; - audio_info_t info; - - AUDIO_INITINFO(&info); +static void sig_callback(pa_mainloop_api *api, pa_signal_event*e, int sig, void *userdata) { + struct userdata *u = userdata; - info.record.gain = pa_cvolume_avg(&s->hw_volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM; - assert(info.record.gain <= AUDIO_MAX_GAIN); + assert(u); - if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) { - if (errno == EINVAL) - pa_log("AUDIO_SETINFO: Unsupported volume."); - else - pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno)); - return -1; + if (u->sink) { + pa_sink_get_volume(u->sink); + pa_sink_get_mute(u->sink); } - return 0; + if (u->source) + pa_source_get_volume(u->source); } static int pa_solaris_auto_format(int fd, int mode, pa_sample_spec *ss) { @@ -490,6 +543,7 @@ static int pa_solaris_set_buffer(int fd, int buffer_size) { AUDIO_INITINFO(&info); + info.play.buffer_size = buffer_size; info.record.buffer_size = buffer_size; if (ioctl(fd, AUDIO_SETINFO, &info) < 0) { @@ -503,7 +557,7 @@ static int pa_solaris_set_buffer(int fd, int buffer_size) { return 0; } -int pa__init(pa_core *c, pa_module*m) { +int pa__init(pa_module *m) { struct userdata *u = NULL; const char *p; int fd = -1; @@ -513,9 +567,10 @@ int pa__init(pa_core *c, pa_module*m) { pa_sample_spec ss; pa_channel_map map; pa_modargs *ma = NULL; - struct timeval tv; char *t; - assert(c && m); + struct pollfd *pollfd; + + pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { pa_log("failed to parse module arguments."); @@ -540,7 +595,7 @@ int pa__init(pa_core *c, pa_module*m) { goto fail; } - ss = c->default_sample_spec; + ss = m->core->default_sample_spec; if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) { pa_log("failed to parse sample specification"); goto fail; @@ -554,55 +609,18 @@ int pa__init(pa_core *c, pa_module*m) { if (pa_solaris_auto_format(fd, mode, &ss) < 0) goto fail; - if ((mode != O_WRONLY) && (buffer_size >= 1)) - if (pa_solaris_set_buffer(fd, buffer_size) < 0) - goto fail; + if (pa_solaris_set_buffer(fd, buffer_size) < 0) + goto fail; u = pa_xmalloc(sizeof(struct userdata)); - u->core = c; + u->core = m->core; - if (mode != O_WRONLY) { - u->source = pa_source_new(c, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, &map); - assert(u->source); - u->source->userdata = u; - u->source->get_latency = source_get_latency_cb; - u->source->get_hw_volume = source_get_hw_volume_cb; - u->source->set_hw_volume = source_set_hw_volume_cb; - pa_source_set_owner(u->source, m); - pa_source_set_description(u->source, t = pa_sprintf_malloc("Solaris PCM on '%s'", p)); - pa_xfree(t); - u->source->is_hardware = 1; - } else - u->source = NULL; - - if (mode != O_RDONLY) { - u->sink = pa_sink_new(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, &map); - assert(u->sink); - u->sink->get_latency = sink_get_latency_cb; - u->sink->get_hw_volume = sink_get_hw_volume_cb; - u->sink->set_hw_volume = sink_set_hw_volume_cb; - u->sink->get_hw_mute = sink_get_hw_mute_cb; - u->sink->set_hw_mute = sink_set_hw_mute_cb; - u->sink->userdata = u; - pa_sink_set_owner(u->sink, m); - pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Solaris PCM on '%s'", p)); - pa_xfree(t); - u->sink->is_hardware = 1; - } else - u->sink = NULL; - - assert(u->source || u->sink); - - u->io = pa_iochannel_new(c->mainloop, u->source ? fd : -1, u->sink ? fd : 0); - assert(u->io); - pa_iochannel_set_callback(u->io, io_callback, u); u->fd = fd; - u->memchunk.memblock = NULL; - u->memchunk.length = 0; + pa_memchunk_reset(&u->memchunk); /* We use this to get a reasonable chunk size */ - u->page_size = sysconf(_SC_PAGESIZE); + u->page_size = PA_PAGE_SIZE; u->frame_size = pa_frame_size(&ss); u->buffer_size = buffer_size; @@ -610,37 +628,91 @@ int pa__init(pa_core *c, pa_module*m) { u->written_bytes = 0; u->read_bytes = 0; - u->sink_underflow = 1; - u->module = m; m->userdata = u; - u->poll_timeout = pa_bytes_to_usec(u->buffer_size / 10, &ss); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop); + + u->rtpoll = pa_rtpoll_new(); + pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq); + + pa_rtpoll_set_timer_periodic(u->rtpoll, pa_bytes_to_usec(u->buffer_size / 10, &ss)); - pa_gettimeofday(&tv); - pa_timeval_add(&tv, u->poll_timeout); + u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1); + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + pollfd->fd = fd; + pollfd->events = 0; + pollfd->revents = 0; - u->timer = c->mainloop->time_new(c->mainloop, &tv, timer_cb, u); - assert(u->timer); + if (mode != O_WRONLY) { + u->source = pa_source_new(m->core, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, &map); + pa_assert(u->source); + + u->source->userdata = u; + u->source->parent.process_msg = source_process_msg; + + pa_source_set_module(u->source, m); + pa_source_set_description(u->source, t = pa_sprintf_malloc("Solaris PCM on '%s'", p)); + pa_xfree(t); + pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); + pa_source_set_rtpoll(u->source, u->rtpoll); + + u->source->flags = PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY|PA_SOURCE_HW_VOLUME_CTRL; + u->source->refresh_volume = 1; + } else + u->source = NULL; + + if (mode != O_RDONLY) { + u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, &map); + pa_assert(u->sink); + + u->sink->userdata = u; + u->sink->parent.process_msg = sink_process_msg; + + pa_sink_set_module(u->sink, m); + pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Solaris PCM on '%s'", p)); + pa_xfree(t); + pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); + pa_sink_set_rtpoll(u->sink, u->rtpoll); + + u->sink->flags = PA_SINK_HARDWARE|PA_SINK_LATENCY|PA_SINK_HW_VOLUME_CTRL; + u->sink->refresh_volume = 1; + u->sink->refresh_mute = 1; + } else + u->sink = NULL; + + pa_assert(u->source || u->sink); u->sig = pa_signal_new(SIGPOLL, sig_callback, u); - assert(u->sig); + pa_assert(u->sig); ioctl(u->fd, I_SETSIG, S_MSG); - pa_modargs_free(ma); + if (!(u->thread = pa_thread_new(thread_func, u))) { + pa_log("Failed to create thread."); + goto fail; + } /* Read mixer settings */ if (u->source) - source_get_hw_volume_cb(u->source); + pa_asyncmsgq_send(u->thread_mq.inq, PA_MSGOBJECT(u->source), PA_SOURCE_MESSAGE_GET_VOLUME, &u->source->volume, 0, NULL); if (u->sink) { - sink_get_hw_volume_cb(u->sink); - sink_get_hw_mute_cb(u->sink); + pa_asyncmsgq_send(u->thread_mq.inq, PA_MSGOBJECT(u->sink), PA_SINK_MESSAGE_GET_VOLUME, &u->sink->volume, 0, NULL); + pa_asyncmsgq_send(u->thread_mq.inq, PA_MSGOBJECT(u->sink), PA_SINK_MESSAGE_GET_MUTE, &u->sink->muted, 0, NULL); } + if (u->sink) + pa_sink_put(u->sink); + if (u->source) + pa_source_put(u->source); + + pa_modargs_free(ma); + return 0; fail: - if (fd >= 0) + if (u) + pa__done(m); + else if (fd >= 0) close(fd); if (ma) @@ -649,31 +721,47 @@ fail: return -1; } -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module *m) { struct userdata *u; - assert(c && m); + + pa_assert(m); if (!(u = m->userdata)) return; - if (u->timer) - c->mainloop->time_free(u->timer); ioctl(u->fd, I_SETSIG, 0); pa_signal_free(u->sig); - if (u->memchunk.memblock) - pa_memblock_unref(u->memchunk.memblock); + if (u->sink) + pa_sink_unlink(u->sink); - if (u->sink) { - pa_sink_disconnect(u->sink); - pa_sink_unref(u->sink); + if (u->source) + pa_source_unlink(u->source); + + if (u->thread) { + pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); + pa_thread_free(u->thread); } - if (u->source) { - pa_source_disconnect(u->source); + pa_thread_mq_done(&u->thread_mq); + + if (u->sink) + pa_sink_unref(u->sink); + + if (u->source) pa_source_unref(u->source); - } - pa_iochannel_free(u->io); + if (u->memchunk.memblock) + pa_memblock_unref(u->memchunk.memblock); + + if (u->rtpoll_item) + pa_rtpoll_item_free(u->rtpoll_item); + + if (u->rtpoll) + pa_rtpoll_free(u->rtpoll); + + if (u->fd >= 0) + close(u->fd); + pa_xfree(u); } diff --git a/src/modules/module-suspend-on-idle.c b/src/modules/module-suspend-on-idle.c new file mode 100644 index 00000000..5a711390 --- /dev/null +++ b/src/modules/module-suspend-on-idle.c @@ -0,0 +1,473 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/xmalloc.h> +#include <pulse/timeval.h> + +#include <pulsecore/core.h> +#include <pulsecore/sink-input.h> +#include <pulsecore/source-output.h> +#include <pulsecore/modargs.h> +#include <pulsecore/log.h> +#include <pulsecore/namereg.h> + +#include "module-suspend-on-idle-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering") +PA_MODULE_DESCRIPTION("When a sink/source is idle for too long, suspend it") +PA_MODULE_VERSION(PACKAGE_VERSION) + +static const char* const valid_modargs[] = { + "timeout", + NULL, +}; + +struct userdata { + pa_core *core; + pa_usec_t timeout; + pa_hashmap *device_infos; + pa_hook_slot + *sink_new_slot, + *source_new_slot, + *sink_unlink_slot, + *source_unlink_slot, + *sink_state_changed_slot, + *source_state_changed_slot; + + pa_hook_slot + *sink_input_new_slot, + *source_output_new_slot, + *sink_input_unlink_slot, + *source_output_unlink_slot, + *sink_input_move_slot, + *source_output_move_slot, + *sink_input_move_post_slot, + *source_output_move_post_slot, + *sink_input_state_changed_slot, + *source_output_state_changed_slot; +}; + +struct device_info { + struct userdata *userdata; + pa_sink *sink; + pa_source *source; + struct timeval last_use; + pa_time_event *time_event; +}; + +static void timeout_cb(pa_mainloop_api*a, pa_time_event* e, const struct timeval *tv, void *userdata) { + struct device_info *d = userdata; + + pa_assert(d); + + d->userdata->core->mainloop->time_restart(d->time_event, NULL); + + if (d->sink && pa_sink_used_by(d->sink) <= 0 && pa_sink_get_state(d->sink) != PA_SINK_SUSPENDED) { + pa_log_info("Sink %s idle for too long, suspending ...", d->sink->name); + pa_sink_suspend(d->sink, TRUE); + } + + if (d->source && pa_source_used_by(d->source) <= 0 && pa_source_get_state(d->source) != PA_SOURCE_SUSPENDED) { + pa_log_info("Source %s idle for too long, suspending ...", d->source->name); + pa_source_suspend(d->source, TRUE); + } +} + +static void restart(struct device_info *d) { + struct timeval tv; + pa_assert(d); + + pa_gettimeofday(&tv); + d->last_use = tv; + pa_timeval_add(&tv, d->userdata->timeout*1000000); + d->userdata->core->mainloop->time_restart(d->time_event, &tv); + + if (d->sink) + pa_log_debug("Sink %s becomes idle.", d->sink->name); + if (d->source) + pa_log_debug("Source %s becomes idle.", d->source->name); +} + +static void resume(struct device_info *d) { + pa_assert(d); + + d->userdata->core->mainloop->time_restart(d->time_event, NULL); + + if (d->sink) { + pa_sink_suspend(d->sink, FALSE); + + pa_log_debug("Sink %s becomes busy.", d->sink->name); + } + + if (d->source) { + pa_source_suspend(d->source, FALSE); + + pa_log_debug("Source %s becomes busy.", d->source->name); + } +} + +static pa_hook_result_t sink_input_new_hook_cb(pa_core *c, pa_sink_input *s, struct userdata *u) { + struct device_info *d; + + pa_assert(c); + pa_sink_input_assert_ref(s); + pa_assert(u); + + if ((d = pa_hashmap_get(u->device_infos, s->sink))) + resume(d); + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_output_new_hook_cb(pa_core *c, pa_source_output *s, struct userdata *u) { + struct device_info *d; + + pa_assert(c); + pa_source_output_assert_ref(s); + pa_assert(u); + + if ((d = pa_hashmap_get(u->device_infos, s->source))) + resume(d); + + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_input_unlink_hook_cb(pa_core *c, pa_sink_input *s, struct userdata *u) { + pa_assert(c); + pa_sink_input_assert_ref(s); + pa_assert(u); + + if (pa_sink_used_by(s->sink) <= 0) { + struct device_info *d; + if ((d = pa_hashmap_get(u->device_infos, s->sink))) + restart(d); + } + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_output_unlink_hook_cb(pa_core *c, pa_source_output *s, struct userdata *u) { + pa_assert(c); + pa_source_output_assert_ref(s); + pa_assert(u); + + if (pa_source_used_by(s->source) <= 0) { + struct device_info *d; + if ((d = pa_hashmap_get(u->device_infos, s->source))) + restart(d); + } + + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_input_move_hook_cb(pa_core *c, pa_sink_input *s, struct userdata *u) { + pa_assert(c); + pa_sink_input_assert_ref(s); + pa_assert(u); + + if (pa_sink_used_by(s->sink) <= 1) { + struct device_info *d; + if ((d = pa_hashmap_get(u->device_infos, s->sink))) + restart(d); + } + + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_input_move_post_hook_cb(pa_core *c, pa_sink_input *s, struct userdata *u) { + struct device_info *d; + pa_assert(c); + pa_sink_input_assert_ref(s); + pa_assert(u); + + if ((d = pa_hashmap_get(u->device_infos, s->sink))) + resume(d); + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_output_move_hook_cb(pa_core *c, pa_source_output *s, struct userdata *u) { + pa_assert(c); + pa_source_output_assert_ref(s); + pa_assert(u); + + if (pa_source_used_by(s->source) <= 1) { + struct device_info *d; + + if ((d = pa_hashmap_get(u->device_infos, s->source))) + restart(d); + } + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_output_move_post_hook_cb(pa_core *c, pa_source_output *s, struct userdata *u) { + struct device_info *d; + pa_assert(c); + pa_source_output_assert_ref(s); + pa_assert(u); + + if ((d = pa_hashmap_get(u->device_infos, s->source))) + resume(d); + + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_input_state_changed_hook_cb(pa_core *c, pa_sink_input *s, struct userdata *u) { + struct device_info *d; + pa_sink_input_state_t state; + pa_assert(c); + pa_sink_input_assert_ref(s); + pa_assert(u); + + state = pa_sink_input_get_state(s); + if (state == PA_SINK_INPUT_RUNNING || state == PA_SINK_INPUT_DRAINED) + if ((d = pa_hashmap_get(u->device_infos, s->sink))) + resume(d); + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_output_state_changed_hook_cb(pa_core *c, pa_source_output *s, struct userdata *u) { + struct device_info *d; + pa_source_output_state_t state; + pa_assert(c); + pa_source_output_assert_ref(s); + pa_assert(u); + + state = pa_source_output_get_state(s); + if (state == PA_SOURCE_OUTPUT_RUNNING) + if ((d = pa_hashmap_get(u->device_infos, s->source))) + resume(d); + + return PA_HOOK_OK; +} + +static pa_hook_result_t device_new_hook_cb(pa_core *c, pa_object *o, struct userdata *u) { + struct device_info *d; + pa_source *source; + pa_sink *sink; + + pa_assert(c); + pa_object_assert_ref(o); + pa_assert(u); + + source = pa_source_isinstance(o) ? PA_SOURCE(o) : NULL; + sink = pa_sink_isinstance(o) ? PA_SINK(o) : NULL; + + pa_assert(source || sink); + + d = pa_xnew(struct device_info, 1); + d->userdata = u; + d->source = source ? pa_source_ref(source) : NULL; + d->sink = sink ? pa_sink_ref(sink) : NULL; + d->time_event = c->mainloop->time_new(c->mainloop, NULL, timeout_cb, d); + pa_hashmap_put(u->device_infos, o, d); + + if ((d->sink && pa_sink_used_by(d->sink) <= 0) || + (d->source && pa_source_used_by(d->source) <= 0)) + restart(d); + + return PA_HOOK_OK; +} + +static void device_info_free(struct device_info *d) { + pa_assert(d); + + if (d->source) + pa_source_unref(d->source); + if (d->sink) + pa_sink_unref(d->sink); + + d->userdata->core->mainloop->time_free(d->time_event); + + pa_xfree(d); +} + +static pa_hook_result_t device_unlink_hook_cb(pa_core *c, pa_object *o, struct userdata *u) { + struct device_info *d; + + pa_assert(c); + pa_object_assert_ref(o); + pa_assert(u); + + if ((d = pa_hashmap_remove(u->device_infos, o))) + device_info_free(d); + + return PA_HOOK_OK; +} + +static pa_hook_result_t device_state_changed_hook_cb(pa_core *c, pa_object *o, struct userdata *u) { + struct device_info *d; + + pa_assert(c); + pa_object_assert_ref(o); + pa_assert(u); + + if (!(d = pa_hashmap_get(u->device_infos, o))) + return PA_HOOK_OK; + + if (pa_sink_isinstance(o)) { + pa_sink *s = PA_SINK(o); + pa_sink_state_t state = pa_sink_get_state(s); + + if (pa_sink_used_by(s) <= 0) { + + if (PA_SINK_OPENED(state)) + restart(d); + + } + + } else if (pa_source_isinstance(o)) { + pa_source *s = PA_SOURCE(o); + pa_source_state_t state = pa_source_get_state(s); + + if (pa_source_used_by(s) <= 0) { + + if (PA_SOURCE_OPENED(state)) + restart(d); + } + } + + return PA_HOOK_OK; +} + +int pa__init(pa_module*m) { + pa_modargs *ma = NULL; + struct userdata *u; + uint32_t timeout = 1; + uint32_t idx; + pa_sink *sink; + pa_source *source; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments."); + goto fail; + } + + if (pa_modargs_get_value_u32(ma, "timeout", &timeout) < 0) { + pa_log("Failed to parse timeout value."); + goto fail; + } + + m->userdata = u = pa_xnew(struct userdata, 1); + u->core = m->core; + u->timeout = timeout; + u->device_infos = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + + for (sink = pa_idxset_first(m->core->sinks, &idx); sink; sink = pa_idxset_next(m->core->sinks, &idx)) + device_new_hook_cb(m->core, PA_OBJECT(sink), u); + + for (source = pa_idxset_first(m->core->sources, &idx); source; source = pa_idxset_next(m->core->sources, &idx)) + device_new_hook_cb(m->core, PA_OBJECT(source), u); + + u->sink_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_NEW_POST], (pa_hook_cb_t) device_new_hook_cb, u); + u->source_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_NEW_POST], (pa_hook_cb_t) device_new_hook_cb, u); + u->sink_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK_POST], (pa_hook_cb_t) device_unlink_hook_cb, u); + u->source_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK_POST], (pa_hook_cb_t) device_unlink_hook_cb, u); + u->sink_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_STATE_CHANGED], (pa_hook_cb_t) device_state_changed_hook_cb, u); + u->source_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_STATE_CHANGED], (pa_hook_cb_t) device_state_changed_hook_cb, u); + + u->sink_input_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_PUT], (pa_hook_cb_t) sink_input_new_hook_cb, u); + u->source_output_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PUT], (pa_hook_cb_t) source_output_new_hook_cb, u); + u->sink_input_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK_POST], (pa_hook_cb_t) sink_input_unlink_hook_cb, u); + u->source_output_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK_POST], (pa_hook_cb_t) source_output_unlink_hook_cb, u); + u->sink_input_move_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE], (pa_hook_cb_t) sink_input_move_hook_cb, u); + u->source_output_move_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE], (pa_hook_cb_t) source_output_move_hook_cb, u); + u->sink_input_move_post_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_POST], (pa_hook_cb_t) sink_input_move_post_hook_cb, u); + u->source_output_move_post_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_POST], (pa_hook_cb_t) source_output_move_post_hook_cb, u); + u->sink_input_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED], (pa_hook_cb_t) sink_input_state_changed_hook_cb, u); + u->source_output_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_STATE_CHANGED], (pa_hook_cb_t) source_output_state_changed_hook_cb, u); + + + pa_modargs_free(ma); + return 0; + +fail: + + if (ma) + pa_modargs_free(ma); + + return -1; +} + +void pa__done(pa_module*m) { + struct userdata *u; + struct device_info *d; + + pa_assert(m); + + if (!m->userdata) + return; + + u = m->userdata; + + if (u->sink_new_slot) + pa_hook_slot_free(u->sink_new_slot); + if (u->sink_unlink_slot) + pa_hook_slot_free(u->sink_unlink_slot); + if (u->sink_state_changed_slot) + pa_hook_slot_free(u->sink_state_changed_slot); + + if (u->source_new_slot) + pa_hook_slot_free(u->source_new_slot); + if (u->source_unlink_slot) + pa_hook_slot_free(u->source_unlink_slot); + if (u->source_state_changed_slot) + pa_hook_slot_free(u->source_state_changed_slot); + + if (u->sink_input_new_slot) + pa_hook_slot_free(u->sink_input_new_slot); + if (u->sink_input_unlink_slot) + pa_hook_slot_free(u->sink_input_unlink_slot); + if (u->sink_input_move_slot) + pa_hook_slot_free(u->sink_input_move_slot); + if (u->sink_input_move_post_slot) + pa_hook_slot_free(u->sink_input_move_post_slot); + if (u->sink_input_state_changed_slot) + pa_hook_slot_free(u->sink_input_state_changed_slot); + + if (u->source_output_new_slot) + pa_hook_slot_free(u->source_output_new_slot); + if (u->source_output_unlink_slot) + pa_hook_slot_free(u->source_output_unlink_slot); + if (u->source_output_move_slot) + pa_hook_slot_free(u->source_output_move_slot); + if (u->source_output_move_post_slot) + pa_hook_slot_free(u->source_output_move_post_slot); + if (u->source_output_state_changed_slot) + pa_hook_slot_free(u->source_output_state_changed_slot); + + while ((d = pa_hashmap_steal_first(u->device_infos))) + device_info_free(d); + + pa_hashmap_free(u->device_infos, NULL, NULL); + + pa_xfree(u); +} diff --git a/src/modules/module-tunnel.c b/src/modules/module-tunnel.c index 288e049e..b96d46b3 100644 --- a/src/modules/module-tunnel.c +++ b/src/modules/module-tunnel.c @@ -596,12 +596,12 @@ static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t } #ifdef TUNNEL_SINK - snprintf(name, sizeof(name), "Tunnel from host %s, user %s, sink %s", + pa_snprintf(name, sizeof(name), "Tunnel from host %s, user %s, sink %s", pa_get_host_name(hn, sizeof(hn)), pa_get_user_name(un, sizeof(un)), u->sink->name); #else - snprintf(name, sizeof(name), "Tunnel from host %s, user %s, source %s", + pa_snprintf(name, sizeof(name), "Tunnel from host %s, user %s, source %s", pa_get_host_name(hn, sizeof(hn)), pa_get_user_name(un, sizeof(un)), u->source->name); diff --git a/src/modules/module-volume-restore.c b/src/modules/module-volume-restore.c index 61a17aef..77e6174f 100644 --- a/src/modules/module-volume-restore.c +++ b/src/modules/module-volume-restore.c @@ -26,7 +26,6 @@ #endif #include <unistd.h> -#include <assert.h> #include <string.h> #include <errno.h> #include <sys/types.h> @@ -35,6 +34,7 @@ #include <ctype.h> #include <pulse/xmalloc.h> +#include <pulse/volume.h> #include <pulsecore/core-error.h> #include <pulsecore/module.h> @@ -44,9 +44,7 @@ #include <pulsecore/core-subscribe.h> #include <pulsecore/sink-input.h> #include <pulsecore/source-output.h> -#include <pulsecore/core-util.h> #include <pulsecore/namereg.h> -#include <pulse/volume.h> #include "module-volume-restore-symdef.h" @@ -85,8 +83,8 @@ static pa_cvolume* parse_volume(const char *s, pa_cvolume *v) { long k; unsigned i; - assert(s); - assert(v); + pa_assert(s); + pa_assert(v); if (!isdigit(*s)) return NULL; @@ -170,7 +168,7 @@ static int load_rules(struct userdata *u) { continue; } - assert(ln == buf_source); + pa_assert(ln == buf_source); if (buf_volume[0]) { if (!parse_volume(buf_volume, &v)) { @@ -297,8 +295,8 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 struct rule *r; char *name; - assert(c); - assert(u); + pa_assert(c); + pa_assert(u); if (t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW) && t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE) && @@ -313,7 +311,7 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 if (!si->client || !(name = client_name(si->client))) return; } else { - assert((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT); + pa_assert((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT); if (!(so = pa_idxset_get_by_index(c->source_outputs, idx))) return; @@ -341,7 +339,7 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 u->modified = 1; } } else { - assert(so); + pa_assert(so); if (!r->source || strcmp(so->source->name, r->source) != 0) { pa_log_info("Saving source for <%s>", r->name); @@ -363,7 +361,7 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 r->sink = pa_xstrdup(si->sink->name); r->source = NULL; } else { - assert(so); + pa_assert(so); r->volume_is_set = 0; r->sink = NULL; r->source = pa_xstrdup(so->source->name); @@ -378,7 +376,7 @@ static pa_hook_result_t sink_input_hook_callback(pa_core *c, pa_sink_input_new_d struct rule *r; char *name; - assert(data); + pa_assert(data); if (!data->client || !(name = client_name(data->client))) return PA_HOOK_OK; @@ -396,6 +394,8 @@ static pa_hook_result_t sink_input_hook_callback(pa_core *c, pa_sink_input_new_d } } + pa_xfree(name); + return PA_HOOK_OK; } @@ -403,7 +403,7 @@ static pa_hook_result_t source_output_hook_callback(pa_core *c, pa_source_output struct rule *r; char *name; - assert(data); + pa_assert(data); if (!data->client || !(name = client_name(data->client))) return PA_HOOK_OK; @@ -418,12 +418,11 @@ static pa_hook_result_t source_output_hook_callback(pa_core *c, pa_source_output return PA_HOOK_OK; } -int pa__init(pa_core *c, pa_module*m) { +int pa__init(pa_module*m) { pa_modargs *ma = NULL; struct userdata *u; - assert(c); - assert(m); + pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { pa_log("Failed to parse module arguments"); @@ -442,16 +441,15 @@ int pa__init(pa_core *c, pa_module*m) { if (load_rules(u) < 0) goto fail; - u->subscription = pa_subscription_new(c, PA_SUBSCRIPTION_MASK_SINK_INPUT|PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT, subscribe_callback, u); - u->sink_input_hook_slot = pa_hook_connect(&c->hook_sink_input_new, (pa_hook_cb_t) sink_input_hook_callback, u); - u->source_output_hook_slot = pa_hook_connect(&c->hook_source_output_new, (pa_hook_cb_t) source_output_hook_callback, u); + u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_SINK_INPUT|PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT, subscribe_callback, u); + u->sink_input_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], (pa_hook_cb_t) sink_input_hook_callback, u); + u->source_output_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NEW], (pa_hook_cb_t) source_output_hook_callback, u); pa_modargs_free(ma); return 0; fail: - pa__done(c, m); - + pa__done(m); if (ma) pa_modargs_free(ma); @@ -460,7 +458,7 @@ fail: static void free_func(void *p, void *userdata) { struct rule *r = p; - assert(r); + pa_assert(r); pa_xfree(r->name); pa_xfree(r->sink); @@ -468,11 +466,10 @@ static void free_func(void *p, void *userdata) { pa_xfree(r); } -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module*m) { struct userdata* u; - assert(c); - assert(m); + pa_assert(m); if (!(u = m->userdata)) return; diff --git a/src/modules/module-x11-bell.c b/src/modules/module-x11-bell.c index b9c4ad49..4eacbb1e 100644 --- a/src/modules/module-x11-bell.c +++ b/src/modules/module-x11-bell.c @@ -26,7 +26,6 @@ #endif #include <stdio.h> -#include <assert.h> #include <stdlib.h> #include <string.h> @@ -67,30 +66,21 @@ static const char* const valid_modargs[] = { NULL }; -static int ring_bell(struct userdata *u, int percent) { - pa_sink *s; - assert(u); - - if (!(s = pa_namereg_get(u->core, u->sink_name, PA_NAMEREG_SINK, 1))) { - pa_log("Invalid sink: %s", u->sink_name); - return -1; - } - - pa_scache_play_item(u->core, u->scache_item, s, (percent*PA_VOLUME_NORM)/100); - return 0; -} - static int x11_event_callback(pa_x11_wrapper *w, XEvent *e, void *userdata) { XkbBellNotifyEvent *bne; struct userdata *u = userdata; - assert(w && e && u && u->x11_wrapper == w); + + pa_assert(w); + pa_assert(e); + pa_assert(u); + pa_assert(u->x11_wrapper == w); if (((XkbEvent*) e)->any.xkb_type != XkbBellNotify) return 0; bne = (XkbBellNotifyEvent*) e; - if (ring_bell(u, bne->percent) < 0) { + if (pa_scache_play_item_by_name(u->core, u->scache_item, u->sink_name, (bne->percent*PA_VOLUME_NORM)/100, 1) < 0) { pa_log_info("Ringing bell failed, reverting to X11 device bell."); XkbForceDeviceBell(pa_x11_wrapper_get_display(w), bne->device, bne->bell_class, bne->bell_id, bne->percent); } @@ -98,25 +88,27 @@ static int x11_event_callback(pa_x11_wrapper *w, XEvent *e, void *userdata) { return 1; } -int pa__init(pa_core *c, pa_module*m) { +int pa__init(pa_module*m) { + struct userdata *u = NULL; pa_modargs *ma = NULL; int major, minor; unsigned int auto_ctrls, auto_values; - assert(c && m); + + pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { - pa_log("failed to parse module arguments"); + pa_log("Failed to parse module arguments"); goto fail; } - m->userdata = u = pa_xmalloc(sizeof(struct userdata)); - u->core = c; + m->userdata = u = pa_xnew(struct userdata, 1); + u->core = m->core; u->scache_item = pa_xstrdup(pa_modargs_get_value(ma, "sample", "x11-bell")); u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL)); u->x11_client = NULL; - if (!(u->x11_wrapper = pa_x11_wrapper_get(c, pa_modargs_get_value(ma, "display", NULL)))) + if (!(u->x11_wrapper = pa_x11_wrapper_get(m->core, pa_modargs_get_value(ma, "display", NULL)))) goto fail; major = XkbMajorVersion; @@ -130,7 +122,6 @@ int pa__init(pa_core *c, pa_module*m) { major = XkbMajorVersion; minor = XkbMinorVersion; - if (!XkbQueryExtension(pa_x11_wrapper_get_display(u->x11_wrapper), NULL, &u->xkb_event_base, NULL, &major, &minor)) { pa_log("XkbQueryExtension() failed"); goto fail; @@ -150,14 +141,21 @@ int pa__init(pa_core *c, pa_module*m) { fail: if (ma) pa_modargs_free(ma); - if (m->userdata) - pa__done(c, m); + + pa__done(m); + return -1; } -void pa__done(pa_core *c, pa_module*m) { - struct userdata *u = m->userdata; - assert(c && m && u); +void pa__done(pa_module*m) { + struct userdata *u; + + pa_assert(m); + + if (!m->userdata) + return; + + u = m->userdata; pa_xfree(u->scache_item); pa_xfree(u->sink_name); diff --git a/src/modules/module-x11-publish.c b/src/modules/module-x11-publish.c index fd1d532f..e0550e20 100644 --- a/src/modules/module-x11-publish.c +++ b/src/modules/module-x11-publish.c @@ -26,7 +26,6 @@ #endif #include <stdio.h> -#include <assert.h> #include <stdlib.h> #include <string.h> #include <unistd.h> @@ -76,7 +75,7 @@ struct userdata { }; static int load_key(struct userdata *u, const char*fn) { - assert(u); + pa_assert(u); u->auth_cookie_in_property = 0; @@ -93,7 +92,7 @@ static int load_key(struct userdata *u, const char*fn) { if (pa_authkey_load_auto(fn, u->auth_cookie, sizeof(u->auth_cookie)) < 0) return -1; - pa_log_debug("loading cookie from disk."); + pa_log_debug("Loading cookie from disk."); if (pa_authkey_prop_put(u->core, PA_NATIVE_COOKIE_PROPERTY_NAME, u->auth_cookie, sizeof(u->auth_cookie)) >= 0) u->auth_cookie_in_property = 1; @@ -101,7 +100,7 @@ static int load_key(struct userdata *u, const char*fn) { return 0; } -int pa__init(pa_core *c, pa_module*m) { +int pa__init(pa_module*m) { struct userdata *u; pa_modargs *ma = NULL; char hn[256], un[128]; @@ -110,23 +109,25 @@ int pa__init(pa_core *c, pa_module*m) { char *s; pa_strlist *l; + pa_assert(m); + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { pa_log("failed to parse module arguments"); goto fail; } m->userdata = u = pa_xmalloc(sizeof(struct userdata)); - u->core = c; + u->core = m->core; u->id = NULL; u->auth_cookie_in_property = 0; if (load_key(u, pa_modargs_get_value(ma, "cookie", NULL)) < 0) goto fail; - if (!(u->x11_wrapper = pa_x11_wrapper_get(c, pa_modargs_get_value(ma, "display", NULL)))) + if (!(u->x11_wrapper = pa_x11_wrapper_get(m->core, pa_modargs_get_value(ma, "display", NULL)))) goto fail; - if (!(l = pa_property_get(c, PA_NATIVE_SERVER_PROPERTY_NAME))) + if (!(l = pa_property_get(m->core, PA_NATIVE_SERVER_PROPERTY_NAME))) goto fail; s = pa_strlist_tostring(l); @@ -154,13 +155,14 @@ fail: if (ma) pa_modargs_free(ma); - pa__done(c, m); + pa__done(m); return -1; } -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module*m) { struct userdata*u; - assert(c && m); + + pa_assert(m); if (!(u = m->userdata)) return; @@ -185,7 +187,7 @@ void pa__done(pa_core *c, pa_module*m) { pa_x11_wrapper_unref(u->x11_wrapper); if (u->auth_cookie_in_property) - pa_authkey_prop_unref(c, PA_NATIVE_COOKIE_PROPERTY_NAME); + pa_authkey_prop_unref(m->core, PA_NATIVE_COOKIE_PROPERTY_NAME); pa_xfree(u->id); pa_xfree(u); diff --git a/src/modules/module-x11-xsmp.c b/src/modules/module-x11-xsmp.c new file mode 100644 index 00000000..3e353caf --- /dev/null +++ b/src/modules/module-x11-xsmp.c @@ -0,0 +1,195 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <X11/Xlib.h> +#include <X11/SM/SMlib.h> + +#include <pulse/xmalloc.h> +#include <pulse/util.h> + +#include <pulsecore/iochannel.h> +#include <pulsecore/sink.h> +#include <pulsecore/core-scache.h> +#include <pulsecore/modargs.h> +#include <pulsecore/namereg.h> +#include <pulsecore/log.h> +#include <pulsecore/core-util.h> + +#include "module-x11-xsmp-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering") +PA_MODULE_DESCRIPTION("X11 session management") +PA_MODULE_VERSION(PACKAGE_VERSION) + +static int ice_in_use = 0; + +static const char* const valid_modargs[] = { + NULL +}; + +static void die_cb(SmcConn connection, SmPointer client_data){ + pa_core *c = PA_CORE(client_data); + + pa_log_debug("Got die message from XSM. Exiting..."); + + pa_core_assert_ref(c); + c->mainloop->quit(c->mainloop, 0); +} + +static void save_complete_cb(SmcConn connection, SmPointer client_data) { +} + +static void shutdown_cancelled_cb(SmcConn connection, SmPointer client_data) { + SmcSaveYourselfDone(connection, True); +} + +static void save_yourself_cb(SmcConn connection, SmPointer client_data, int save_type, Bool _shutdown, int interact_style, Bool fast) { + SmcSaveYourselfDone(connection, True); +} + +static void ice_io_cb(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_event_flags_t flags, void *userdata) { + IceConn connection = userdata; + + if (IceProcessMessages(connection, NULL, NULL) == IceProcessMessagesIOError) { + IceSetShutdownNegotiation(connection, False); + IceCloseConnection(connection); + } +} + +static void new_ice_connection(IceConn connection, IcePointer client_data, Bool opening, IcePointer *watch_data) { + pa_core *c = client_data; + + pa_assert(c); + + if (opening) + *watch_data = c->mainloop->io_new(c->mainloop, IceConnectionNumber(connection), PA_IO_EVENT_INPUT, ice_io_cb, connection); + else + c->mainloop->io_free(*watch_data); +} + +int pa__init(pa_module*m) { + + pa_modargs *ma = NULL; + char t[256], *vendor, *client_id; + SmcCallbacks callbacks; + SmProp prop_program, prop_user; + SmProp *prop_list[2]; + SmPropValue val_program, val_user; + SmcConn connection; + + pa_assert(m); + + if (ice_in_use) { + pa_log("module-x11-xsmp may no be loaded twice."); + return -1; + } + + IceAddConnectionWatch(new_ice_connection, m->core); + ice_in_use = 1; + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments"); + goto fail; + } + + if (!getenv("SESSION_MANAGER")) { + pa_log("X11 session manager not running."); + goto fail; + } + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.die.callback = die_cb; + callbacks.die.client_data = m->core; + callbacks.save_yourself.callback = save_yourself_cb; + callbacks.save_yourself.client_data = m->core; + callbacks.save_complete.callback = save_complete_cb; + callbacks.save_complete.client_data = m->core; + callbacks.shutdown_cancelled.callback = shutdown_cancelled_cb; + callbacks.shutdown_cancelled.client_data = m->core; + + if (!(m->userdata = connection = SmcOpenConnection( + NULL, m->core, + SmProtoMajor, SmProtoMinor, + SmcSaveYourselfProcMask | SmcDieProcMask | SmcSaveCompleteProcMask | SmcShutdownCancelledProcMask, + &callbacks, NULL, &client_id, + sizeof(t), t))) { + + pa_log("Failed to open connection to session manager: %s", t); + goto fail; + } + + prop_program.name = (char*) SmProgram; + prop_program.type = (char*) SmARRAY8; + val_program.value = (char*) PACKAGE_NAME; + val_program.length = strlen(val_program.value); + prop_program.num_vals = 1; + prop_program.vals = &val_program; + prop_list[0] = &prop_program; + + prop_user.name = (char*) SmUserID; + prop_user.type = (char*) SmARRAY8; + pa_get_user_name(t, sizeof(t)); + val_user.value = t; + val_user.length = strlen(val_user.value); + prop_user.num_vals = 1; + prop_user.vals = &val_user; + prop_list[1] = &prop_user; + + SmcSetProperties(connection, PA_ELEMENTSOF(prop_list), prop_list); + + pa_log_info("Connected to session manager '%s' as '%s'.", vendor = SmcVendor(connection), client_id); + free(vendor); + free(client_id); + + pa_modargs_free(ma); + + return 0; + +fail: + if (ma) + pa_modargs_free(ma); + + pa__done(m); + + return -1; +} + +void pa__done(pa_module*m) { + pa_assert(m); + + if (m->userdata) + SmcCloseConnection(m->userdata, 0, NULL); + + if (ice_in_use) { + IceRemoveConnectionWatch(new_ice_connection, m->core); + ice_in_use = 0; + } +} diff --git a/src/modules/module-zeroconf-publish.c b/src/modules/module-zeroconf-publish.c index 69508ad0..113686cf 100644 --- a/src/modules/module-zeroconf-publish.c +++ b/src/modules/module-zeroconf-publish.c @@ -26,7 +26,6 @@ #endif #include <stdio.h> -#include <assert.h> #include <stdlib.h> #include <string.h> #include <unistd.h> @@ -35,11 +34,11 @@ #include <avahi-client/publish.h> #include <avahi-common/alternative.h> #include <avahi-common/error.h> +#include <avahi-common/domain.h> #include <pulse/xmalloc.h> #include <pulse/util.h> -#include <pulsecore/autoload.h> #include <pulsecore/sink.h> #include <pulsecore/source.h> #include <pulsecore/native-common.h> @@ -71,56 +70,52 @@ struct service { struct userdata *userdata; AvahiEntryGroup *entry_group; char *service_name; - char *name; - enum { UNPUBLISHED, PUBLISHED_REAL, PUBLISHED_AUTOLOAD } published ; - - struct { - int valid; - pa_namereg_type_t type; - uint32_t index; - } loaded; - - struct { - int valid; - pa_namereg_type_t type; - uint32_t index; - } autoload; + pa_object *device; }; struct userdata { pa_core *core; AvahiPoll *avahi_poll; AvahiClient *client; + pa_hashmap *services; - pa_dynarray *sink_dynarray, *source_dynarray, *autoload_dynarray; - pa_subscription *subscription; char *service_name; AvahiEntryGroup *main_entry_group; uint16_t port; + + pa_hook_slot *sink_new_slot, *source_new_slot, *sink_unlink_slot, *source_unlink_slot, *sink_changed_slot, *source_changed_slot; }; -static void get_service_data(struct userdata *u, struct service *s, pa_sample_spec *ret_ss, char **ret_description) { - assert(u && s && s->loaded.valid && ret_ss && ret_description); +static void get_service_data(struct service *s, pa_sample_spec *ret_ss, pa_channel_map *ret_map, const char **ret_name, const char **ret_description) { + pa_assert(s); + pa_assert(ret_ss); + pa_assert(ret_description); + + if (pa_sink_isinstance(s->device)) { + pa_sink *sink = PA_SINK(s->device); - if (s->loaded.type == PA_NAMEREG_SINK) { - pa_sink *sink = pa_idxset_get_by_index(u->core->sinks, s->loaded.index); - assert(sink); *ret_ss = sink->sample_spec; + *ret_map = sink->channel_map; + *ret_name = sink->name; *ret_description = sink->description; - } else if (s->loaded.type == PA_NAMEREG_SOURCE) { - pa_source *source = pa_idxset_get_by_index(u->core->sources, s->loaded.index); - assert(source); + + } else if (pa_source_isinstance(s->device)) { + pa_source *source = PA_SOURCE(s->device); + *ret_ss = source->sample_spec; + *ret_map = source->channel_map; + *ret_name = source->name; *ret_description = source->description; } else - assert(0); + pa_assert_not_reached(); } static AvahiStringList* txt_record_server_data(pa_core *c, AvahiStringList *l) { char s[128]; - assert(c); + + pa_assert(c); l = avahi_string_list_add_pair(l, "server-version", PACKAGE_NAME" "PACKAGE_VERSION); l = avahi_string_list_add_pair(l, "user-name", pa_get_user_name(s, sizeof(s))); @@ -130,325 +125,217 @@ static AvahiStringList* txt_record_server_data(pa_core *c, AvahiStringList *l) { return l; } -static int publish_service(struct userdata *u, struct service *s); +static int publish_service(struct service *s); static void service_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) { struct service *s = userdata; - if (state == AVAHI_ENTRY_GROUP_COLLISION) { - char *t; + pa_assert(s); + + switch (state) { + + case AVAHI_ENTRY_GROUP_ESTABLISHED: + pa_log_info("Successfully established service %s.", s->service_name); + break; + + case AVAHI_ENTRY_GROUP_COLLISION: { + char *t; + + t = avahi_alternative_service_name(s->service_name); + pa_log_info("Name collision, renaming %s to %s.", s->service_name, t); + pa_xfree(s->service_name); + s->service_name = t; + + publish_service(s); + break; + } + + case AVAHI_ENTRY_GROUP_FAILURE: { + pa_log("Failed to register service: %s", avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g)))); - t = avahi_alternative_service_name(s->service_name); - pa_xfree(s->service_name); - s->service_name = t; + avahi_entry_group_free(g); + s->entry_group = NULL; - publish_service(s->userdata, s); + break; + } + + case AVAHI_ENTRY_GROUP_UNCOMMITED: + case AVAHI_ENTRY_GROUP_REGISTERING: + ; } } -static int publish_service(struct userdata *u, struct service *s) { +static void service_free(struct service *s); + +static int publish_service(struct service *s) { int r = -1; AvahiStringList *txt = NULL; + const char *description = NULL, *name = NULL; + pa_sample_spec ss; + pa_channel_map map; + char cm[PA_CHANNEL_MAP_SNPRINT_MAX]; - assert(u); - assert(s); + pa_assert(s); - if (!u->client || avahi_client_get_state(u->client) != AVAHI_CLIENT_S_RUNNING) + if (!s->userdata->client || avahi_client_get_state(s->userdata->client) != AVAHI_CLIENT_S_RUNNING) return 0; - if ((s->published == PUBLISHED_REAL && s->loaded.valid) || - (s->published == PUBLISHED_AUTOLOAD && s->autoload.valid && !s->loaded.valid)) - return 0; - - if (s->published != UNPUBLISHED) { + if (!s->entry_group) { + if (!(s->entry_group = avahi_entry_group_new(s->userdata->client, service_entry_group_callback, s))) { + pa_log("avahi_entry_group_new(): %s", avahi_strerror(avahi_client_errno(s->userdata->client))); + goto finish; + } + } else avahi_entry_group_reset(s->entry_group); - s->published = UNPUBLISHED; - } - if (s->loaded.valid || s->autoload.valid) { - pa_namereg_type_t type; + txt = txt_record_server_data(s->userdata->core, txt); - if (!s->entry_group) { - if (!(s->entry_group = avahi_entry_group_new(u->client, service_entry_group_callback, s))) { - pa_log("avahi_entry_group_new(): %s", avahi_strerror(avahi_client_errno(u->client))); - goto finish; - } - } + get_service_data(s, &ss, &map, &name, &description); + txt = avahi_string_list_add_pair(txt, "device", name); + txt = avahi_string_list_add_printf(txt, "rate=%u", ss.rate); + txt = avahi_string_list_add_printf(txt, "channels=%u", ss.channels); + txt = avahi_string_list_add_pair(txt, "format", pa_sample_format_to_string(ss.format)); + txt = avahi_string_list_add_pair(txt, "channel_map", pa_channel_map_snprint(cm, sizeof(cm), &map)); - txt = avahi_string_list_add_pair(txt, "device", s->name); - txt = txt_record_server_data(u->core, txt); - - if (s->loaded.valid) { - char *description; - pa_sample_spec ss; - - get_service_data(u, s, &ss, &description); - - txt = avahi_string_list_add_printf(txt, "rate=%u", ss.rate); - txt = avahi_string_list_add_printf(txt, "channels=%u", ss.channels); - txt = avahi_string_list_add_pair(txt, "format", pa_sample_format_to_string(ss.format)); - if (description) - txt = avahi_string_list_add_pair(txt, "description", description); - - type = s->loaded.type; - } else if (s->autoload.valid) - type = s->autoload.type; - - if (avahi_entry_group_add_service_strlst( - s->entry_group, - AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, - 0, - s->service_name, - type == PA_NAMEREG_SINK ? SERVICE_TYPE_SINK : SERVICE_TYPE_SOURCE, - NULL, - NULL, - u->port, - txt) < 0) { - - pa_log("avahi_entry_group_add_service_strlst(): %s", avahi_strerror(avahi_client_errno(u->client))); - goto finish; - } + if (avahi_entry_group_add_service_strlst( + s->entry_group, + AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, + 0, + s->service_name, + pa_sink_isinstance(s->device) ? SERVICE_TYPE_SINK : SERVICE_TYPE_SOURCE, + NULL, + NULL, + s->userdata->port, + txt) < 0) { - if (avahi_entry_group_commit(s->entry_group) < 0) { - pa_log("avahi_entry_group_commit(): %s", avahi_strerror(avahi_client_errno(u->client))); - goto finish; - } + pa_log("avahi_entry_group_add_service_strlst(): %s", avahi_strerror(avahi_client_errno(s->userdata->client))); + goto finish; + } - if (s->loaded.valid) - s->published = PUBLISHED_REAL; - else if (s->autoload.valid) - s->published = PUBLISHED_AUTOLOAD; + if (avahi_entry_group_commit(s->entry_group) < 0) { + pa_log("avahi_entry_group_commit(): %s", avahi_strerror(avahi_client_errno(s->userdata->client))); + goto finish; } r = 0; + pa_log_debug("Successfully created entry group for %s.", s->service_name); finish: - if (s->published == UNPUBLISHED) { - /* Remove this service */ - - if (s->entry_group) - avahi_entry_group_free(s->entry_group); + /* Remove this service */ + if (r < 0) + service_free(s); - pa_hashmap_remove(u->services, s->name); - pa_xfree(s->name); - pa_xfree(s->service_name); - pa_xfree(s); - } - - if (txt) - avahi_string_list_free(txt); + avahi_string_list_free(txt); return r; } -static struct service *get_service(struct userdata *u, const char *name, const char *description) { +static struct service *get_service(struct userdata *u, pa_object *device) { struct service *s; - char hn[64]; + char hn[64], un[64]; + const char *n; + + pa_assert(u); + pa_object_assert_ref(device); - if ((s = pa_hashmap_get(u->services, name))) + if ((s = pa_hashmap_get(u->services, device))) return s; s = pa_xnew(struct service, 1); s->userdata = u; s->entry_group = NULL; - s->published = UNPUBLISHED; - s->name = pa_xstrdup(name); - s->loaded.valid = s->autoload.valid = 0; - s->service_name = pa_sprintf_malloc("%s on %s", description ? description : s->name, pa_get_host_name(hn, sizeof(hn))); - - pa_hashmap_put(u->services, s->name, s); - - return s; -} - -static int publish_sink(struct userdata *u, pa_sink *s) { - struct service *svc; - int ret; - assert(u && s); - - svc = get_service(u, s->name, s->description); - if (svc->loaded.valid) - return publish_service(u, svc); - - svc->loaded.valid = 1; - svc->loaded.type = PA_NAMEREG_SINK; - svc->loaded.index = s->index; - - if ((ret = publish_service(u, svc)) < 0) - return ret; - - pa_dynarray_put(u->sink_dynarray, s->index, svc); - return ret; -} - -static int publish_source(struct userdata *u, pa_source *s) { - struct service *svc; - int ret; - - assert(u && s); - - svc = get_service(u, s->name, s->description); - if (svc->loaded.valid) - return publish_service(u, svc); - - svc->loaded.valid = 1; - svc->loaded.type = PA_NAMEREG_SOURCE; - svc->loaded.index = s->index; + s->device = device; + + if (pa_sink_isinstance(device)) { + if (!(n = PA_SINK(device)->description)) + n = PA_SINK(device)->name; + } else { + if (!(n = PA_SOURCE(device)->description)) + n = PA_SOURCE(device)->name; + } - pa_dynarray_put(u->source_dynarray, s->index, svc); + s->service_name = pa_truncate_utf8(pa_sprintf_malloc("%s@%s: %s", + pa_get_user_name(un, sizeof(un)), + pa_get_host_name(hn, sizeof(hn)), + n), + AVAHI_LABEL_MAX-1); - if ((ret = publish_service(u, svc)) < 0) - return ret; + pa_hashmap_put(u->services, s->device, s); - pa_dynarray_put(u->sink_dynarray, s->index, svc); - return ret; + return s; } -static int publish_autoload(struct userdata *u, pa_autoload_entry *s) { - struct service *svc; - int ret; +static void service_free(struct service *s) { + pa_assert(s); - assert(u && s); + pa_hashmap_remove(s->userdata->services, s->device); - svc = get_service(u, s->name, NULL); - if (svc->autoload.valid) - return publish_service(u, svc); - - svc->autoload.valid = 1; - svc->autoload.type = s->type; - svc->autoload.index = s->index; - - if ((ret = publish_service(u, svc)) < 0) - return ret; + if (s->entry_group) { + pa_log_debug("Removing entry group for %s.", s->service_name); + avahi_entry_group_free(s->entry_group); + } - pa_dynarray_put(u->autoload_dynarray, s->index, svc); - return ret; + pa_xfree(s->service_name); + pa_xfree(s); } -static int remove_sink(struct userdata *u, uint32_t idx) { - struct service *svc; - assert(u && idx != PA_INVALID_INDEX); - - if (!(svc = pa_dynarray_get(u->sink_dynarray, idx))) - return 0; - - if (!svc->loaded.valid || svc->loaded.type != PA_NAMEREG_SINK) - return 0; +static pa_hook_result_t device_new_or_changed_cb(pa_core *c, pa_object *o, struct userdata *u) { + pa_assert(c); + pa_object_assert_ref(o); - svc->loaded.valid = 0; - pa_dynarray_put(u->sink_dynarray, idx, NULL); + publish_service(get_service(u, o)); - return publish_service(u, svc); + return PA_HOOK_OK; } -static int remove_source(struct userdata *u, uint32_t idx) { - struct service *svc; - assert(u && idx != PA_INVALID_INDEX); - - if (!(svc = pa_dynarray_get(u->source_dynarray, idx))) - return 0; +static pa_hook_result_t device_unlink_cb(pa_core *c, pa_object *o, struct userdata *u) { + struct service *s; - if (!svc->loaded.valid || svc->loaded.type != PA_NAMEREG_SOURCE) - return 0; + pa_assert(c); + pa_object_assert_ref(o); - svc->loaded.valid = 0; - pa_dynarray_put(u->source_dynarray, idx, NULL); + if ((s = pa_hashmap_get(u->services, o))) + service_free(s); - return publish_service(u, svc); + return PA_HOOK_OK; } -static int remove_autoload(struct userdata *u, uint32_t idx) { - struct service *svc; - assert(u && idx != PA_INVALID_INDEX); - - if (!(svc = pa_dynarray_get(u->autoload_dynarray, idx))) - return 0; - - if (!svc->autoload.valid) - return 0; - - svc->autoload.valid = 0; - pa_dynarray_put(u->autoload_dynarray, idx, NULL); - - return publish_service(u, svc); -} +static int publish_main_service(struct userdata *u); -static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { +static void main_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) { struct userdata *u = userdata; - assert(u && c); - - switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) - case PA_SUBSCRIPTION_EVENT_SINK: { - if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { - pa_sink *sink; - - if ((sink = pa_idxset_get_by_index(c->sinks, idx))) { - if (publish_sink(u, sink) < 0) - goto fail; - } - } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { - if (remove_sink(u, idx) < 0) - goto fail; - } + pa_assert(u); - break; + switch (state) { - case PA_SUBSCRIPTION_EVENT_SOURCE: + case AVAHI_ENTRY_GROUP_ESTABLISHED: + pa_log_info("Successfully established main service."); + break; - if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { - pa_source *source; + case AVAHI_ENTRY_GROUP_COLLISION: { + char *t; - if ((source = pa_idxset_get_by_index(c->sources, idx))) { - if (publish_source(u, source) < 0) - goto fail; - } - } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { - if (remove_source(u, idx) < 0) - goto fail; - } + t = avahi_alternative_service_name(u->service_name); + pa_log_info("Name collision: renaming main service %s to %s.", u->service_name, t); + pa_xfree(u->service_name); + u->service_name = t; + publish_main_service(u); break; + } - case PA_SUBSCRIPTION_EVENT_AUTOLOAD: - if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { - pa_autoload_entry *autoload; - - if ((autoload = pa_idxset_get_by_index(c->autoload_idxset, idx))) { - if (publish_autoload(u, autoload) < 0) - goto fail; - } - } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { - if (remove_autoload(u, idx) < 0) - goto fail; - } + case AVAHI_ENTRY_GROUP_FAILURE: { + pa_log("Failed to register main service: %s", avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g)))); + avahi_entry_group_free(g); + u->main_entry_group = NULL; break; - } - - return; - -fail: - if (u->subscription) { - pa_subscription_free(u->subscription); - u->subscription = NULL; - } -} - -static int publish_main_service(struct userdata *u); - -static void main_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) { - struct userdata *u = userdata; - assert(u); - - if (state == AVAHI_ENTRY_GROUP_COLLISION) { - char *t; - - t = avahi_alternative_service_name(u->service_name); - pa_xfree(u->service_name); - u->service_name = t; + } - publish_main_service(u); + case AVAHI_ENTRY_GROUP_UNCOMMITED: + case AVAHI_ENTRY_GROUP_REGISTERING: + break; } } @@ -456,6 +343,8 @@ static int publish_main_service(struct userdata *u) { AvahiStringList *txt = NULL; int r = -1; + pa_assert(u); + if (!u->main_entry_group) { if (!(u->main_entry_group = avahi_entry_group_new(u->client, main_entry_group_callback, u))) { pa_log("avahi_entry_group_new() failed: %s", avahi_strerror(avahi_client_errno(u->client))); @@ -464,7 +353,7 @@ static int publish_main_service(struct userdata *u) { } else avahi_entry_group_reset(u->main_entry_group); - txt = txt_record_server_data(u->core, NULL); + txt = txt_record_server_data(u->core, txt); if (avahi_entry_group_add_service_strlst( u->main_entry_group, @@ -497,26 +386,18 @@ fail: static int publish_all_services(struct userdata *u) { pa_sink *sink; pa_source *source; - pa_autoload_entry *autoload; int r = -1; uint32_t idx; - assert(u); + pa_assert(u); pa_log_debug("Publishing services in Zeroconf"); - for (sink = pa_idxset_first(u->core->sinks, &idx); sink; sink = pa_idxset_next(u->core->sinks, &idx)) - if (publish_sink(u, sink) < 0) - goto fail; - - for (source = pa_idxset_first(u->core->sources, &idx); source; source = pa_idxset_next(u->core->sources, &idx)) - if (publish_source(u, source) < 0) - goto fail; + for (sink = PA_SINK(pa_idxset_first(u->core->sinks, &idx)); sink; sink = PA_SINK(pa_idxset_next(u->core->sinks, &idx))) + publish_service(get_service(u, PA_OBJECT(sink))); - if (u->core->autoload_idxset) - for (autoload = pa_idxset_first(u->core->autoload_idxset, &idx); autoload; autoload = pa_idxset_next(u->core->autoload_idxset, &idx)) - if (publish_autoload(u, autoload) < 0) - goto fail; + for (source = PA_SOURCE(pa_idxset_first(u->core->sources, &idx)); source; source = PA_SOURCE(pa_idxset_next(u->core->sources, &idx))) + publish_service(get_service(u, PA_OBJECT(source))); if (publish_main_service(u) < 0) goto fail; @@ -527,38 +408,44 @@ fail: return r; } -static void unpublish_all_services(struct userdata *u, int rem) { +static void unpublish_all_services(struct userdata *u, pa_bool_t rem) { void *state = NULL; struct service *s; - assert(u); + pa_assert(u); pa_log_debug("Unpublishing services in Zeroconf"); while ((s = pa_hashmap_iterate(u->services, &state, NULL))) { if (s->entry_group) { if (rem) { + pa_log_debug("Removing entry group for %s.", s->service_name); avahi_entry_group_free(s->entry_group); s->entry_group = NULL; - } else + } else { avahi_entry_group_reset(s->entry_group); + pa_log_debug("Resetting entry group for %s.", s->service_name); + } } - - s->published = UNPUBLISHED; } if (u->main_entry_group) { if (rem) { + pa_log_debug("Removing main entry group."); avahi_entry_group_free(u->main_entry_group); u->main_entry_group = NULL; - } else + } else { avahi_entry_group_reset(u->main_entry_group); + pa_log_debug("Resetting main entry group."); + } } } static void client_callback(AvahiClient *c, AvahiClientState state, void *userdata) { struct userdata *u = userdata; - assert(c); + + pa_assert(c); + pa_assert(u); u->client = c; @@ -568,13 +455,17 @@ static void client_callback(AvahiClient *c, AvahiClientState state, void *userda break; case AVAHI_CLIENT_S_COLLISION: - unpublish_all_services(u, 0); + pa_log_debug("Host name collision"); + unpublish_all_services(u, FALSE); break; case AVAHI_CLIENT_FAILURE: if (avahi_client_errno(c) == AVAHI_ERR_DISCONNECTED) { int error; - unpublish_all_services(u, 1); + + pa_log_debug("Avahi daemon disconnected."); + + unpublish_all_services(u, TRUE); avahi_client_free(u->client); if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error))) @@ -587,11 +478,12 @@ static void client_callback(AvahiClient *c, AvahiClientState state, void *userda } } -int pa__init(pa_core *c, pa_module*m) { +int pa__init(pa_module*m) { + struct userdata *u; uint32_t port = PA_NATIVE_DEFAULT_PORT; pa_modargs *ma = NULL; - char hn[256]; + char hn[256], un[256]; int error; if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { @@ -599,30 +491,29 @@ int pa__init(pa_core *c, pa_module*m) { goto fail; } - if (pa_modargs_get_value_u32(ma, "port", &port) < 0 || port == 0 || port >= 0xFFFF) { + if (pa_modargs_get_value_u32(ma, "port", &port) < 0 || port <= 0 || port > 0xFFFF) { pa_log("invalid port specified."); goto fail; } m->userdata = u = pa_xnew(struct userdata, 1); - u->core = c; + u->core = m->core; u->port = (uint16_t) port; - u->avahi_poll = pa_avahi_poll_new(c->mainloop); + u->avahi_poll = pa_avahi_poll_new(m->core->mainloop); - u->services = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); - u->sink_dynarray = pa_dynarray_new(); - u->source_dynarray = pa_dynarray_new(); - u->autoload_dynarray = pa_dynarray_new(); + u->services = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); - u->subscription = pa_subscription_new(c, - PA_SUBSCRIPTION_MASK_SINK| - PA_SUBSCRIPTION_MASK_SOURCE| - PA_SUBSCRIPTION_MASK_AUTOLOAD, subscribe_callback, u); + u->sink_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_NEW_POST], (pa_hook_cb_t) device_new_or_changed_cb, u); + u->sink_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_DESCRIPTION_CHANGED], (pa_hook_cb_t) device_new_or_changed_cb, u); + u->sink_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], (pa_hook_cb_t) device_unlink_cb, u); + u->source_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_NEW_POST], (pa_hook_cb_t) device_new_or_changed_cb, u); + u->source_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_DESCRIPTION_CHANGED], (pa_hook_cb_t) device_new_or_changed_cb, u); + u->source_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], (pa_hook_cb_t) device_unlink_cb, u); u->main_entry_group = NULL; - u->service_name = pa_xstrdup(pa_get_host_name(hn, sizeof(hn))); + u->service_name = pa_truncate_utf8(pa_sprintf_malloc("%s@%s", pa_get_user_name(un, sizeof(un)), pa_get_host_name(hn, sizeof(hn))), AVAHI_LABEL_MAX); if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error))) { pa_log("pa_avahi_client_new() failed: %s", avahi_strerror(error)); @@ -634,7 +525,7 @@ int pa__init(pa_core *c, pa_module*m) { return 0; fail: - pa__done(c, m); + pa__done(m); if (ma) pa_modargs_free(ma); @@ -642,41 +533,34 @@ fail: return -1; } -static void service_free(void *p, void *userdata) { - struct service *s = p; - struct userdata *u = userdata; - - assert(s); - assert(u); - - if (s->entry_group) - avahi_entry_group_free(s->entry_group); - - pa_xfree(s->service_name); - pa_xfree(s->name); - pa_xfree(s); -} - -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module*m) { struct userdata*u; - assert(c && m); + pa_assert(m); if (!(u = m->userdata)) return; - if (u->services) - pa_hashmap_free(u->services, service_free, u); + if (u->services) { + struct service *s; - if (u->subscription) - pa_subscription_free(u->subscription); + while ((s = pa_hashmap_get_first(u->services))) + service_free(s); - if (u->sink_dynarray) - pa_dynarray_free(u->sink_dynarray, NULL, NULL); - if (u->source_dynarray) - pa_dynarray_free(u->source_dynarray, NULL, NULL); - if (u->autoload_dynarray) - pa_dynarray_free(u->autoload_dynarray, NULL, NULL); + pa_hashmap_free(u->services, NULL, NULL); + } + if (u->sink_new_slot) + pa_hook_slot_free(u->sink_new_slot); + if (u->source_new_slot) + pa_hook_slot_free(u->source_new_slot); + if (u->sink_changed_slot) + pa_hook_slot_free(u->sink_changed_slot); + if (u->source_changed_slot) + pa_hook_slot_free(u->source_changed_slot); + if (u->sink_unlink_slot) + pa_hook_slot_free(u->sink_unlink_slot); + if (u->source_unlink_slot) + pa_hook_slot_free(u->source_unlink_slot); if (u->main_entry_group) avahi_entry_group_free(u->main_entry_group); @@ -690,4 +574,3 @@ void pa__done(pa_core *c, pa_module*m) { pa_xfree(u->service_name); pa_xfree(u); } - diff --git a/src/modules/oss-util.c b/src/modules/oss-util.c index fb531468..25e45a35 100644 --- a/src/modules/oss-util.c +++ b/src/modules/oss-util.c @@ -26,7 +26,6 @@ #include <config.h> #endif -#include <assert.h> #include <sys/soundcard.h> #include <sys/ioctl.h> #include <stdio.h> @@ -37,9 +36,11 @@ #include <sys/stat.h> #include <fcntl.h> +#include <pulse/xmalloc.h> #include <pulsecore/core-error.h> #include <pulsecore/core-util.h> #include <pulsecore/log.h> +#include <pulsecore/macro.h> #include "oss-util.h" @@ -47,46 +48,43 @@ int pa_oss_open(const char *device, int *mode, int* pcaps) { int fd = -1; int caps; - assert(device && mode && (*mode == O_RDWR || *mode == O_RDONLY || *mode == O_WRONLY)); + pa_assert(device); + pa_assert(mode); + pa_assert(*mode == O_RDWR || *mode == O_RDONLY || *mode == O_WRONLY); if(!pcaps) pcaps = ∩︀ if (*mode == O_RDWR) { - if ((fd = open(device, O_RDWR|O_NDELAY)) >= 0) { - int dcaps, *tcaps; + if ((fd = open(device, O_RDWR|O_NDELAY|O_NOCTTY)) >= 0) { ioctl(fd, SNDCTL_DSP_SETDUPLEX, 0); - tcaps = pcaps ? pcaps : &dcaps; - - if (ioctl(fd, SNDCTL_DSP_GETCAPS, tcaps) < 0) { + if (ioctl(fd, SNDCTL_DSP_GETCAPS, pcaps) < 0) { pa_log("SNDCTL_DSP_GETCAPS: %s", pa_cstrerror(errno)); goto fail; } - if (*tcaps & DSP_CAP_DUPLEX) + if (*pcaps & DSP_CAP_DUPLEX) goto success; pa_log_warn("'%s' doesn't support full duplex", device); - close(fd); + pa_close(fd); } - if ((fd = open(device, (*mode = O_WRONLY)|O_NDELAY)) < 0) { - if ((fd = open(device, (*mode = O_RDONLY)|O_NDELAY)) < 0) { + if ((fd = open(device, (*mode = O_WRONLY)|O_NDELAY|O_NOCTTY)) < 0) { + if ((fd = open(device, (*mode = O_RDONLY)|O_NDELAY|O_NOCTTY)) < 0) { pa_log("open('%s'): %s", device, pa_cstrerror(errno)); goto fail; } } } else { - if ((fd = open(device, *mode|O_NDELAY)) < 0) { + if ((fd = open(device, *mode|O_NDELAY|O_NOCTTY)) < 0) { pa_log("open('%s'): %s", device, pa_cstrerror(errno)); goto fail; } } -success: - *pcaps = 0; if (ioctl(fd, SNDCTL_DSP_GETCAPS, pcaps) < 0) { @@ -94,12 +92,14 @@ success: goto fail; } +success: + pa_log_debug("capabilities:%s%s%s%s%s%s%s%s%s%s%s%s%s%s", *pcaps & DSP_CAP_BATCH ? " BATCH" : "", #ifdef DSP_CAP_BIND *pcaps & DSP_CAP_BIND ? " BIND" : "", #else - "", + "", #endif *pcaps & DSP_CAP_COPROC ? " COPROC" : "", *pcaps & DSP_CAP_DUPLEX ? " DUPLEX" : "", @@ -122,7 +122,7 @@ success: #ifdef DSP_CAP_MULTI *pcaps & DSP_CAP_MULTI ? " MULTI" : "", #else - "", + "", #endif #ifdef DSP_CAP_OUTPUT *pcaps & DSP_CAP_OUTPUT ? " OUTPUT" : "", @@ -142,13 +142,13 @@ success: #endif *pcaps & DSP_CAP_TRIGGER ? " TRIGGER" : ""); - pa_fd_set_cloexec(fd, 1); + pa_make_fd_cloexec(fd); return fd; fail: if (fd >= 0) - close(fd); + pa_close(fd); return -1; } @@ -166,7 +166,8 @@ int pa_oss_auto_format(int fd, pa_sample_spec *ss) { [PA_SAMPLE_FLOAT32BE] = AFMT_QUERY, /* not supported */ }; - assert(fd >= 0 && ss); + pa_assert(fd >= 0); + pa_assert(ss); orig_format = ss->format; @@ -199,7 +200,7 @@ int pa_oss_auto_format(int fd, pa_sample_spec *ss) { pa_log("SNDCTL_DSP_CHANNELS: %s", pa_cstrerror(errno)); return -1; } - assert(channels > 0); + pa_assert(channels > 0); if (ss->channels != channels) { pa_log_warn("device doesn't support %i channels, using %i channels.", ss->channels, channels); @@ -211,7 +212,7 @@ int pa_oss_auto_format(int fd, pa_sample_spec *ss) { pa_log("SNDCTL_DSP_SPEED: %s", pa_cstrerror(errno)); return -1; } - assert(speed > 0); + pa_assert(speed > 0); if (ss->rate != (unsigned) speed) { pa_log_warn("device doesn't support %i Hz, changed to %i Hz.", ss->rate, speed); @@ -248,27 +249,29 @@ int pa_oss_set_fragments(int fd, int nfrags, int frag_size) { return 0; } -static int pa_oss_get_volume(int fd, int mixer, const pa_sample_spec *ss, pa_cvolume *volume) { +int pa_oss_get_volume(int fd, int mixer, const pa_sample_spec *ss, pa_cvolume *volume) { char cv[PA_CVOLUME_SNPRINT_MAX]; unsigned vol; - assert(fd >= 0); - assert(ss); - assert(volume); + pa_assert(fd >= 0); + pa_assert(ss); + pa_assert(volume); if (ioctl(fd, mixer, &vol) < 0) return -1; + pa_cvolume_reset(volume, ss->channels); + volume->values[0] = ((vol & 0xFF) * PA_VOLUME_NORM) / 100; - if ((volume->channels = ss->channels) >= 2) + if (volume->channels >= 2) volume->values[1] = (((vol >> 8) & 0xFF) * PA_VOLUME_NORM) / 100; pa_log_debug("Read mixer settings: %s", pa_cvolume_snprint(cv, sizeof(cv), volume)); return 0; } -static int pa_oss_set_volume(int fd, int mixer, const pa_sample_spec *ss, const pa_cvolume *volume) { +int pa_oss_set_volume(int fd, long mixer, const pa_sample_spec *ss, const pa_cvolume *volume) { char cv[PA_CVOLUME_SNPRINT_MAX]; unsigned vol; pa_volume_t l, r; @@ -289,40 +292,38 @@ static int pa_oss_set_volume(int fd, int mixer, const pa_sample_spec *ss, const return 0; } -int pa_oss_get_pcm_volume(int fd, const pa_sample_spec *ss, pa_cvolume *volume) { - return pa_oss_get_volume(fd, SOUND_MIXER_READ_PCM, ss, volume); -} +static int get_device_number(const char *dev) { + char buf[PATH_MAX]; + const char *p, *e; -int pa_oss_set_pcm_volume(int fd, const pa_sample_spec *ss, const pa_cvolume *volume) { - return pa_oss_set_volume(fd, SOUND_MIXER_WRITE_PCM, ss, volume); -} + if (readlink(dev, buf, sizeof(buf)) < 0) { + if (errno != EINVAL && errno != ENOLINK) + return -1; -int pa_oss_get_input_volume(int fd, const pa_sample_spec *ss, pa_cvolume *volume) { - return pa_oss_get_volume(fd, SOUND_MIXER_READ_IGAIN, ss, volume); -} + p = dev; + } else + p = buf; + + if ((e = strrchr(p, '/'))) + p = e+1; -int pa_oss_set_input_volume(int fd, const pa_sample_spec *ss, const pa_cvolume *volume) { - return pa_oss_set_volume(fd, SOUND_MIXER_WRITE_IGAIN, ss, volume); + if (p == 0) + return 0; + + p = strchr(p, 0) -1; + + if (*p >= '0' && *p <= '9') + return *p - '0'; + + return -1; } int pa_oss_get_hw_description(const char *dev, char *name, size_t l) { FILE *f; - const char *e = NULL; int n, r = -1; int b = 0; - if (strncmp(dev, "/dev/dsp", 8) == 0) - e = dev+8; - else if (strncmp(dev, "/dev/adsp", 9) == 0) - e = dev+9; - else - return -1; - - if (*e == 0) - n = 0; - else if (*e >= '0' && *e <= '9' && *(e+1) == 0) - n = *e - '0'; - else + if ((n = get_device_number(dev)) < 0) return -1; if (!(f = fopen("/dev/sndstat", "r")) && @@ -357,7 +358,7 @@ int pa_oss_get_hw_description(const char *dev, char *name, size_t l) { if (device == n) { char *k = strchr(line, ':'); - assert(k); + pa_assert(k); k++; k += strspn(k, " "); @@ -373,3 +374,34 @@ int pa_oss_get_hw_description(const char *dev, char *name, size_t l) { fclose(f); return r; } + +static int open_mixer(const char *mixer) { + int fd; + + if ((fd = open(mixer, O_RDWR|O_NDELAY|O_NOCTTY)) >= 0) + return fd; + + return -1; +} + +int pa_oss_open_mixer_for_device(const char *device) { + int n; + char *fn; + int fd; + + if ((n = get_device_number(device)) < 0) + return -1; + + if (n == 0) + if ((fd = open_mixer("/dev/mixer")) >= 0) + return fd; + + fn = pa_sprintf_malloc("/dev/mixer%i", n); + fd = open_mixer(fn); + pa_xfree(fn); + + if (fd < 0) + pa_log_warn("Failed to open mixer '%s': %s", device, pa_cstrerror(errno)); + + return fd; +} diff --git a/src/modules/oss-util.h b/src/modules/oss-util.h index 087e0d22..259a622a 100644 --- a/src/modules/oss-util.h +++ b/src/modules/oss-util.h @@ -33,12 +33,11 @@ int pa_oss_auto_format(int fd, pa_sample_spec *ss); int pa_oss_set_fragments(int fd, int frags, int frag_size); -int pa_oss_get_pcm_volume(int fd, const pa_sample_spec *ss, pa_cvolume *volume); -int pa_oss_set_pcm_volume(int fd, const pa_sample_spec *ss, const pa_cvolume *volume); - -int pa_oss_get_input_volume(int fd, const pa_sample_spec *ss, pa_cvolume *volume); -int pa_oss_set_input_volume(int fd, const pa_sample_spec *ss, const pa_cvolume *volume); +int pa_oss_set_volume(int fd, long mixer, const pa_sample_spec *ss, const pa_cvolume *volume); +int pa_oss_get_volume(int fd, int mixer, const pa_sample_spec *ss, pa_cvolume *volume); int pa_oss_get_hw_description(const char *dev, char *name, size_t l); +int pa_oss_open_mixer_for_device(const char *device); + #endif diff --git a/src/modules/rtp/module-rtp-recv.c b/src/modules/rtp/module-rtp-recv.c index 62ef561f..6c018931 100644 --- a/src/modules/rtp/module-rtp-recv.c +++ b/src/modules/rtp/module-rtp-recv.c @@ -24,7 +24,6 @@ #include <config.h> #endif -#include <assert.h> #include <stdio.h> #include <sys/socket.h> #include <netinet/in.h> @@ -32,6 +31,7 @@ #include <errno.h> #include <string.h> #include <unistd.h> +#include <poll.h> #include <pulse/timeval.h> #include <pulse/xmalloc.h> @@ -47,6 +47,10 @@ #include <pulsecore/modargs.h> #include <pulsecore/namereg.h> #include <pulsecore/sample-util.h> +#include <pulsecore/macro.h> +#include <pulsecore/atomic.h> +#include <pulsecore/rtclock.h> +#include <pulsecore/atomic.h> #include "module-rtp-recv-symdef.h" @@ -66,7 +70,7 @@ PA_MODULE_USAGE( #define DEFAULT_SAP_ADDRESS "224.0.0.56" #define MEMBLOCKQ_MAXLENGTH (1024*170) #define MAX_SESSIONS 16 -#define DEATH_TIMEOUT 20000000 +#define DEATH_TIMEOUT 20 static const char* const valid_modargs[] = { "sink", @@ -76,102 +80,126 @@ static const char* const valid_modargs[] = { struct session { struct userdata *userdata; + PA_LLIST_FIELDS(struct session); pa_sink_input *sink_input; pa_memblockq *memblockq; - pa_time_event *death_event; - - int first_packet; + pa_bool_t first_packet; uint32_t ssrc; uint32_t offset; struct pa_sdp_info sdp_info; pa_rtp_context rtp_context; - pa_io_event* rtp_event; + + pa_rtpoll_item *rtpoll_item; + + pa_atomic_t timestamp; }; struct userdata { pa_module *module; - pa_core *core; pa_sap_context sap_context; pa_io_event* sap_event; - pa_hashmap *by_origin; + pa_time_event *check_death_event; char *sink_name; + PA_LLIST_HEAD(struct session, sessions); + pa_hashmap *by_origin; int n_sessions; }; -static void session_free(struct session *s, int from_hash); +static void session_free(struct session *s); -static int sink_input_peek(pa_sink_input *i, pa_memchunk *chunk) { - struct session *s; - assert(i); - s = i->userdata; +/* Called from I/O thread context */ +static int sink_input_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct session *s = PA_SINK_INPUT(o)->userdata; - return pa_memblockq_peek(s->memblockq, chunk); + switch (code) { + case PA_SINK_INPUT_MESSAGE_GET_LATENCY: + *((pa_usec_t*) data) = pa_bytes_to_usec(pa_memblockq_get_length(s->memblockq), &s->sink_input->sample_spec); + + /* Fall through, the default handler will add in the extra + * latency added by the resampler */ + break; + } + + return pa_sink_input_process_msg(o, code, data, offset, chunk); } -static void sink_input_drop(pa_sink_input *i, const pa_memchunk *chunk, size_t length) { +/* Called from I/O thread context */ +static int sink_input_peek(pa_sink_input *i, size_t length, pa_memchunk *chunk) { struct session *s; - assert(i); - s = i->userdata; + pa_sink_input_assert_ref(i); + pa_assert_se(s = i->userdata); - pa_memblockq_drop(s->memblockq, chunk, length); + return pa_memblockq_peek(s->memblockq, chunk); } -static void sink_input_kill(pa_sink_input* i) { +/* Called from I/O thread context */ +static void sink_input_drop(pa_sink_input *i, size_t length) { struct session *s; - assert(i); - s = i->userdata; + pa_sink_input_assert_ref(i); + pa_assert_se(s = i->userdata); - session_free(s, 1); + pa_memblockq_drop(s->memblockq, length); } -static pa_usec_t sink_input_get_latency(pa_sink_input *i) { +/* Called from main context */ +static void sink_input_kill(pa_sink_input* i) { struct session *s; - assert(i); - s = i->userdata; + pa_sink_input_assert_ref(i); + pa_assert_se(s = i->userdata); - return pa_bytes_to_usec(pa_memblockq_get_length(s->memblockq), &i->sample_spec); + session_free(s); } -static void rtp_event_cb(pa_mainloop_api *m, pa_io_event *e, int fd, pa_io_event_flags_t flags, void *userdata) { - struct session *s = userdata; +/* Called from I/O thread context */ +static int rtpoll_work_cb(pa_rtpoll_item *i) { pa_memchunk chunk; int64_t k, j, delta; - struct timeval tv; + struct timeval now; + struct session *s; + struct pollfd *p; - assert(m); - assert(e); - assert(s); - assert(fd == s->rtp_context.fd); - assert(flags == PA_IO_EVENT_INPUT); + pa_assert_se(s = pa_rtpoll_item_get_userdata(i)); - if (pa_rtp_recv(&s->rtp_context, &chunk, s->userdata->core->mempool) < 0) - return; + p = pa_rtpoll_item_get_pollfd(i, NULL); + + if (p->revents & (POLLERR|POLLNVAL|POLLHUP|POLLOUT)) { + pa_log("poll() signalled bad revents."); + return -1; + } + + if ((p->revents & POLLIN) == 0) + return 0; + + p->revents = 0; + + if (pa_rtp_recv(&s->rtp_context, &chunk, s->userdata->module->core->mempool) < 0) + return 0; if (s->sdp_info.payload != s->rtp_context.payload) { pa_memblock_unref(chunk.memblock); - return; + return 0; } if (!s->first_packet) { - s->first_packet = 1; + s->first_packet = TRUE; s->ssrc = s->rtp_context.ssrc; s->offset = s->rtp_context.timestamp; - if (s->ssrc == s->userdata->core->cookie) - pa_log_warn("WARNING! Detected RTP packet loop!"); + if (s->ssrc == s->userdata->module->core->cookie) + pa_log_warn("Detected RTP packet loop!"); } else { if (s->ssrc != s->rtp_context.ssrc) { pa_memblock_unref(chunk.memblock); - return; + return 0; } } @@ -197,26 +225,49 @@ static void rtp_event_cb(pa_mainloop_api *m, pa_io_event *e, int fd, pa_io_event pa_memblock_unref(chunk.memblock); - /* Reset death timer */ - pa_gettimeofday(&tv); - pa_timeval_add(&tv, DEATH_TIMEOUT); - m->time_restart(s->death_event, &tv); + pa_rtclock_get(&now); + pa_atomic_store(&s->timestamp, now.tv_sec); + + return 1; } -static void death_event_cb(pa_mainloop_api *m, pa_time_event *t, const struct timeval *tv, void *userdata) { - struct session *s = userdata; +/* Called from I/O thread context */ +static void sink_input_attach(pa_sink_input *i) { + struct session *s; + struct pollfd *p; + + pa_sink_input_assert_ref(i); + pa_assert_se(s = i->userdata); + + pa_assert(!s->rtpoll_item); + s->rtpoll_item = pa_rtpoll_item_new(i->sink->rtpoll, PA_RTPOLL_LATE, 1); + + p = pa_rtpoll_item_get_pollfd(s->rtpoll_item, NULL); + p->fd = s->rtp_context.fd; + p->events = POLLIN; + p->revents = 0; + + pa_rtpoll_item_set_work_callback(s->rtpoll_item, rtpoll_work_cb); + pa_rtpoll_item_set_userdata(s->rtpoll_item, s); +} - assert(m); - assert(t); - assert(tv); - assert(s); +/* Called from I/O thread context */ +static void sink_input_detach(pa_sink_input *i) { + struct session *s; + pa_sink_input_assert_ref(i); + pa_assert_se(s = i->userdata); - session_free(s, 1); + pa_assert(s->rtpoll_item); + pa_rtpoll_item_free(s->rtpoll_item); + s->rtpoll_item = NULL; } static int mcast_socket(const struct sockaddr* sa, socklen_t salen) { int af, fd = -1, r, one; + pa_assert(sa); + pa_assert(salen > 0); + af = sa->sa_family; if ((fd = socket(af, SOCK_DGRAM, 0)) < 0) { pa_log("Failed to create socket: %s", pa_cstrerror(errno)); @@ -262,27 +313,34 @@ fail: static struct session *session_new(struct userdata *u, const pa_sdp_info *sdp_info) { struct session *s = NULL; - struct timeval tv; char *c; pa_sink *sink; int fd = -1; pa_memblock *silence; pa_sink_input_new_data data; + struct timeval now; + + pa_assert(u); + pa_assert(sdp_info); if (u->n_sessions >= MAX_SESSIONS) { - pa_log("session limit reached."); + pa_log("Session limit reached."); goto fail; } - if (!(sink = pa_namereg_get(u->core, u->sink_name, PA_NAMEREG_SINK, 1))) { - pa_log("sink does not exist."); + if (!(sink = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK, 1))) { + pa_log("Sink does not exist."); goto fail; } s = pa_xnew0(struct session, 1); s->userdata = u; - s->first_packet = 0; + s->first_packet = FALSE; s->sdp_info = *sdp_info; + s->rtpoll_item = NULL; + + pa_rtclock_get(&now); + pa_atomic_store(&s->timestamp, now.tv_sec); if ((fd = mcast_socket((const struct sockaddr*) &sdp_info->sa, sdp_info->salen)) < 0) goto fail; @@ -299,25 +357,27 @@ static struct session *session_new(struct userdata *u, const pa_sdp_info *sdp_in data.module = u->module; pa_sink_input_new_data_set_sample_spec(&data, &sdp_info->sample_spec); - s->sink_input = pa_sink_input_new(u->core, &data, 0); + s->sink_input = pa_sink_input_new(u->module->core, &data, 0); pa_xfree(c); if (!s->sink_input) { - pa_log("failed to create sink input."); + pa_log("Failed to create sink input."); goto fail; } s->sink_input->userdata = s; + s->sink_input->parent.process_msg = sink_input_process_msg; s->sink_input->peek = sink_input_peek; s->sink_input->drop = sink_input_drop; s->sink_input->kill = sink_input_kill; - s->sink_input->get_latency = sink_input_get_latency; + s->sink_input->attach = sink_input_attach; + s->sink_input->detach = sink_input_detach; - silence = pa_silence_memblock_new(s->userdata->core->mempool, - &s->sink_input->sample_spec, - (pa_bytes_per_second(&s->sink_input->sample_spec)/128/pa_frame_size(&s->sink_input->sample_spec))* - pa_frame_size(&s->sink_input->sample_spec)); + silence = pa_silence_memblock_new( + s->userdata->module->core->mempool, + &s->sink_input->sample_spec, + pa_frame_align(pa_bytes_per_second(&s->sink_input->sample_spec)/128, &s->sink_input->sample_spec)); s->memblockq = pa_memblockq_new( 0, @@ -330,54 +390,44 @@ static struct session *session_new(struct userdata *u, const pa_sdp_info *sdp_in pa_memblock_unref(silence); - s->rtp_event = u->core->mainloop->io_new(u->core->mainloop, fd, PA_IO_EVENT_INPUT, rtp_event_cb, s); - - pa_gettimeofday(&tv); - pa_timeval_add(&tv, DEATH_TIMEOUT); - s->death_event = u->core->mainloop->time_new(u->core->mainloop, &tv, death_event_cb, s); + pa_rtp_context_init_recv(&s->rtp_context, fd, pa_frame_size(&s->sdp_info.sample_spec)); pa_hashmap_put(s->userdata->by_origin, s->sdp_info.origin, s); + u->n_sessions++; + PA_LLIST_PREPEND(struct session, s->userdata->sessions, s); - pa_rtp_context_init_recv(&s->rtp_context, fd, pa_frame_size(&s->sdp_info.sample_spec)); - - pa_log_info("Found new session '%s'", s->sdp_info.session_name); + pa_sink_input_put(s->sink_input); - u->n_sessions++; + pa_log_info("New session '%s'", s->sdp_info.session_name); return s; fail: - if (s) { - if (fd >= 0) - close(fd); + pa_xfree(s); - pa_xfree(s); - } + if (fd >= 0) + pa_close(fd); return NULL; } -static void session_free(struct session *s, int from_hash) { - assert(s); +static void session_free(struct session *s) { + pa_assert(s); pa_log_info("Freeing session '%s'", s->sdp_info.session_name); - s->userdata->core->mainloop->time_free(s->death_event); - s->userdata->core->mainloop->io_free(s->rtp_event); - - if (from_hash) - pa_hashmap_remove(s->userdata->by_origin, s->sdp_info.origin); - - pa_sink_input_disconnect(s->sink_input); + pa_sink_input_unlink(s->sink_input); pa_sink_input_unref(s->sink_input); + PA_LLIST_REMOVE(struct session, s->userdata->sessions, s); + pa_assert(s->userdata->n_sessions >= 1); + s->userdata->n_sessions--; + pa_hashmap_remove(s->userdata->by_origin, s->sdp_info.origin); + pa_memblockq_free(s->memblockq); pa_sdp_info_destroy(&s->sdp_info); pa_rtp_context_destroy(&s->rtp_context); - assert(s->userdata->n_sessions >= 1); - s->userdata->n_sessions--; - pa_xfree(s); } @@ -387,11 +437,11 @@ static void sap_event_cb(pa_mainloop_api *m, pa_io_event *e, int fd, pa_io_event pa_sdp_info info; struct session *s; - assert(m); - assert(e); - assert(u); - assert(fd == u->sap_context.fd); - assert(flags == PA_IO_EVENT_INPUT); + pa_assert(m); + pa_assert(e); + pa_assert(u); + pa_assert(fd == u->sap_context.fd); + pa_assert(flags == PA_IO_EVENT_INPUT); if (pa_sap_recv(&u->sap_context, &goodbye) < 0) return; @@ -402,7 +452,7 @@ static void sap_event_cb(pa_mainloop_api *m, pa_io_event *e, int fd, pa_io_event if (goodbye) { if ((s = pa_hashmap_get(u->by_origin, info.origin))) - session_free(s, 1); + session_free(s); pa_sdp_info_destroy(&info); } else { @@ -412,18 +462,47 @@ static void sap_event_cb(pa_mainloop_api *m, pa_io_event *e, int fd, pa_io_event pa_sdp_info_destroy(&info); } else { - struct timeval tv; - - pa_gettimeofday(&tv); - pa_timeval_add(&tv, DEATH_TIMEOUT); - m->time_restart(s->death_event, &tv); + struct timeval now; + pa_rtclock_get(&now); + pa_atomic_store(&s->timestamp, now.tv_sec); pa_sdp_info_destroy(&info); } } } -int pa__init(pa_core *c, pa_module*m) { +static void check_death_event_cb(pa_mainloop_api *m, pa_time_event *t, const struct timeval *ptv, void *userdata) { + struct session *s, *n; + struct userdata *u = userdata; + struct timeval now; + struct timeval tv; + + pa_assert(m); + pa_assert(t); + pa_assert(ptv); + pa_assert(u); + + pa_rtclock_get(&now); + + pa_log_debug("Checking for dead streams ..."); + + for (s = u->sessions; s; s = n) { + int k; + n = s->next; + + k = pa_atomic_load(&s->timestamp); + + if (k + DEATH_TIMEOUT < now.tv_sec) + session_free(s); + } + + /* Restart timer */ + pa_gettimeofday(&tv); + pa_timeval_add(&tv, DEATH_TIMEOUT*PA_USEC_PER_SEC); + m->time_restart(t, &tv); +} + +int pa__init(pa_module*m) { struct userdata *u; pa_modargs *ma = NULL; struct sockaddr_in sa4; @@ -432,9 +511,9 @@ int pa__init(pa_core *c, pa_module*m) { socklen_t salen; const char *sap_address; int fd = -1; + struct timeval tv; - assert(c); - assert(m); + pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { pa_log("failed to parse module arguments"); @@ -454,7 +533,7 @@ int pa__init(pa_core *c, pa_module*m) { sa = (struct sockaddr*) &sa4; salen = sizeof(sa4); } else { - pa_log("invalid SAP address '%s'", sap_address); + pa_log("Invalid SAP address '%s'", sap_address); goto fail; } @@ -464,15 +543,18 @@ int pa__init(pa_core *c, pa_module*m) { u = pa_xnew(struct userdata, 1); m->userdata = u; u->module = m; - u->core = c; u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL)); - u->n_sessions = 0; - u->sap_event = c->mainloop->io_new(c->mainloop, fd, PA_IO_EVENT_INPUT, sap_event_cb, u); + u->sap_event = m->core->mainloop->io_new(m->core->mainloop, fd, PA_IO_EVENT_INPUT, sap_event_cb, u); + pa_sap_context_init_recv(&u->sap_context, fd); + PA_LLIST_HEAD_INIT(struct session, u->sessions); + u->n_sessions = 0; u->by_origin = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); - pa_sap_context_init_recv(&u->sap_context, fd); + pa_gettimeofday(&tv); + pa_timeval_add(&tv, DEATH_TIMEOUT * PA_USEC_PER_SEC); + u->check_death_event = m->core->mainloop->time_new(m->core->mainloop, &tv, check_death_event_cb, u); pa_modargs_free(ma); @@ -483,27 +565,34 @@ fail: pa_modargs_free(ma); if (fd >= 0) - close(fd); + pa_close(fd); return -1; } -static void free_func(void *p, PA_GCC_UNUSED void *userdata) { - session_free(p, 0); -} - -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module*m) { struct userdata *u; - assert(c); - assert(m); + struct session *s; + + pa_assert(m); if (!(u = m->userdata)) return; - c->mainloop->io_free(u->sap_event); + if (u->sap_event) + m->core->mainloop->io_free(u->sap_event); + + if (u->check_death_event) + m->core->mainloop->time_free(u->check_death_event); + pa_sap_context_destroy(&u->sap_context); - pa_hashmap_free(u->by_origin, free_func, NULL); + if (u->by_origin) { + while ((s = pa_hashmap_get_first(u->by_origin))) + session_free(s); + + pa_hashmap_free(u->by_origin, NULL, NULL); + } pa_xfree(u->sink_name); pa_xfree(u); diff --git a/src/modules/rtp/module-rtp-send.c b/src/modules/rtp/module-rtp-send.c index 8c9e5f23..f36989bd 100644 --- a/src/modules/rtp/module-rtp-send.c +++ b/src/modules/rtp/module-rtp-send.c @@ -25,7 +25,6 @@ #include <config.h> #endif -#include <assert.h> #include <stdio.h> #include <sys/socket.h> #include <netinet/in.h> @@ -48,6 +47,9 @@ #include <pulsecore/core-util.h> #include <pulsecore/modargs.h> #include <pulsecore/namereg.h> +#include <pulsecore/sample-util.h> +#include <pulsecore/macro.h> +#include <pulsecore/socket-util.h> #include "module-rtp-send-symdef.h" @@ -74,7 +76,7 @@ PA_MODULE_USAGE( #define DEFAULT_DESTINATION "224.0.0.56" #define MEMBLOCKQ_MAXLENGTH (1024*170) #define DEFAULT_MTU 1280 -#define SAP_INTERVAL 5000000 +#define SAP_INTERVAL 5 static const char* const valid_modargs[] = { "source", @@ -90,7 +92,6 @@ static const char* const valid_modargs[] = { struct userdata { pa_module *module; - pa_core *core; pa_source_output *source_output; pa_memblockq *memblockq; @@ -102,56 +103,67 @@ struct userdata { pa_time_event *sap_event; }; +/* Called from I/O thread context */ +static int source_output_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u; + pa_assert_se(u = PA_SOURCE_OUTPUT(o)->userdata); + + switch (code) { + case PA_SOURCE_OUTPUT_MESSAGE_GET_LATENCY: + *((pa_usec_t*) data) = pa_bytes_to_usec(pa_memblockq_get_length(u->memblockq), &u->source_output->sample_spec); + + /* Fall through, the default handler will add in the extra + * latency added by the resampler */ + break; + } + + return pa_source_output_process_msg(o, code, data, offset, chunk); +} + +/* Called from I/O thread context */ static void source_output_push(pa_source_output *o, const pa_memchunk *chunk) { struct userdata *u; - assert(o); - u = o->userdata; + pa_source_output_assert_ref(o); + pa_assert_se(u = o->userdata); if (pa_memblockq_push(u->memblockq, chunk) < 0) { - pa_log("Failed to push chunk into memblockq."); + pa_log_warn("Failed to push chunk into memblockq."); return; } pa_rtp_send(&u->rtp_context, u->mtu, u->memblockq); } +/* Called from main context */ static void source_output_kill(pa_source_output* o) { struct userdata *u; - assert(o); - u = o->userdata; + pa_source_output_assert_ref(o); + pa_assert_se(u = o->userdata); pa_module_unload_request(u->module); - pa_source_output_disconnect(u->source_output); + pa_source_output_unlink(u->source_output); pa_source_output_unref(u->source_output); u->source_output = NULL; } -static pa_usec_t source_output_get_latency (pa_source_output *o) { - struct userdata *u; - assert(o); - u = o->userdata; - - return pa_bytes_to_usec(pa_memblockq_get_length(u->memblockq), &o->sample_spec); -} - static void sap_event_cb(pa_mainloop_api *m, pa_time_event *t, const struct timeval *tv, void *userdata) { struct userdata *u = userdata; struct timeval next; - assert(m); - assert(t); - assert(tv); - assert(u); + pa_assert(m); + pa_assert(t); + pa_assert(tv); + pa_assert(u); pa_sap_send(&u->sap_context, 0); pa_gettimeofday(&next); - pa_timeval_add(&next, SAP_INTERVAL); + pa_timeval_add(&next, SAP_INTERVAL * PA_USEC_PER_SEC); m->time_restart(t, &next); } -int pa__init(pa_core *c, pa_module*m) { +int pa__init(pa_module*m) { struct userdata *u; pa_modargs *ma = NULL; const char *dest; @@ -173,21 +185,20 @@ int pa__init(pa_core *c, pa_module*m) { int loop = 0; pa_source_output_new_data data; - assert(c); - assert(m); + pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { - pa_log("failed to parse module arguments"); + pa_log("Failed to parse module arguments"); goto fail; } if (!(s = pa_namereg_get(m->core, pa_modargs_get_value(ma, "source", NULL), PA_NAMEREG_SOURCE, 1))) { - pa_log("source does not exist."); + pa_log("Source does not exist."); goto fail; } if (pa_modargs_get_value_boolean(ma, "loop", &loop) < 0) { - pa_log("failed to parse \"loop\" parameter."); + pa_log("Failed to parse \"loop\" parameter."); goto fail; } @@ -195,12 +206,12 @@ int pa__init(pa_core *c, pa_module*m) { pa_rtp_sample_spec_fixup(&ss); cm = s->channel_map; if (pa_modargs_get_sample_spec(ma, &ss) < 0) { - pa_log("failed to parse sample specification"); + pa_log("Failed to parse sample specification"); goto fail; } if (!pa_rtp_sample_spec_valid(&ss)) { - pa_log("specified sample type not compatible with RTP"); + pa_log("Specified sample type not compatible with RTP"); goto fail; } @@ -209,10 +220,10 @@ int pa__init(pa_core *c, pa_module*m) { payload = pa_rtp_payload_from_sample_spec(&ss); - mtu = (DEFAULT_MTU/pa_frame_size(&ss))*pa_frame_size(&ss); + mtu = pa_frame_align(DEFAULT_MTU, &ss); if (pa_modargs_get_value_u32(ma, "mtu", &mtu) < 0 || mtu < 1 || mtu % pa_frame_size(&ss) != 0) { - pa_log("invalid mtu."); + pa_log("Invalid MTU."); goto fail; } @@ -223,7 +234,7 @@ int pa__init(pa_core *c, pa_module*m) { } if (port & 1) - pa_log_warn("WARNING: port number not even as suggested in RFC3550!"); + pa_log_warn("Port number not even as suggested in RFC3550!"); dest = pa_modargs_get_value(ma, "destination", DEFAULT_DESTINATION); @@ -238,7 +249,7 @@ int pa__init(pa_core *c, pa_module*m) { sap_sa4 = sa4; sap_sa4.sin_port = htons(SAP_PORT); } else { - pa_log("invalid destination '%s'", dest); + pa_log("Invalid destination '%s'", dest); goto fail; } @@ -268,6 +279,12 @@ int pa__init(pa_core *c, pa_module*m) { goto fail; } + /* If the socket queue is full, let's drop packets */ + pa_make_fd_nonblock(fd); + pa_make_udp_socket_low_delay(fd); + pa_make_fd_cloexec(fd); + pa_make_fd_cloexec(sap_fd); + pa_source_output_new_data_init(&data); data.name = "RTP Monitor Stream"; data.driver = __FILE__; @@ -276,21 +293,20 @@ int pa__init(pa_core *c, pa_module*m) { pa_source_output_new_data_set_sample_spec(&data, &ss); pa_source_output_new_data_set_channel_map(&data, &cm); - if (!(o = pa_source_output_new(c, &data, 0))) { + if (!(o = pa_source_output_new(m->core, &data, 0))) { pa_log("failed to create source output."); goto fail; } + o->parent.process_msg = source_output_process_msg; o->push = source_output_push; o->kill = source_output_kill; - o->get_latency = source_output_get_latency; u = pa_xnew(struct userdata, 1); m->userdata = u; o->userdata = u; u->module = m; - u->core = c; u->source_output = o; u->memblockq = pa_memblockq_new( @@ -305,8 +321,7 @@ int pa__init(pa_core *c, pa_module*m) { u->mtu = mtu; k = sizeof(sa_dst); - r = getsockname(fd, (struct sockaddr*) &sa_dst, &k); - assert(r >= 0); + pa_assert_se((r = getsockname(fd, (struct sockaddr*) &sa_dst, &k)) >= 0); n = pa_sprintf_malloc("PulseAudio RTP Stream on %s", pa_get_fqdn(hn, sizeof(hn))); @@ -317,17 +332,19 @@ int pa__init(pa_core *c, pa_module*m) { pa_xfree(n); - pa_rtp_context_init_send(&u->rtp_context, fd, c->cookie, payload, pa_frame_size(&ss)); + pa_rtp_context_init_send(&u->rtp_context, fd, m->core->cookie, payload, pa_frame_size(&ss)); pa_sap_context_init_send(&u->sap_context, sap_fd, p); pa_log_info("RTP stream initialized with mtu %u on %s:%u, SSRC=0x%08x, payload=%u, initial sequence #%u", mtu, dest, port, u->rtp_context.ssrc, payload, u->rtp_context.sequence); - pa_log_info("SDP-Data:\n%s\n"__FILE__": EOF", p); + pa_log_info("SDP-Data:\n%s\nEOF", p); pa_sap_send(&u->sap_context, 0); pa_gettimeofday(&tv); - pa_timeval_add(&tv, SAP_INTERVAL); - u->sap_event = c->mainloop->time_new(c->mainloop, &tv, sap_event_cb, u); + pa_timeval_add(&tv, SAP_INTERVAL * PA_USEC_PER_SEC); + u->sap_event = m->core->mainloop->time_new(m->core->mainloop, &tv, sap_event_cb, u); + + pa_source_output_put(u->source_output); pa_modargs_free(ma); @@ -338,31 +355,31 @@ fail: pa_modargs_free(ma); if (fd >= 0) - close(fd); + pa_close(fd); if (sap_fd >= 0) - close(sap_fd); + pa_close(sap_fd); if (o) { - pa_source_output_disconnect(o); + pa_source_output_unlink(o); pa_source_output_unref(o); } return -1; } -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module*m) { struct userdata *u; - assert(c); - assert(m); + pa_assert(m); if (!(u = m->userdata)) return; - c->mainloop->time_free(u->sap_event); + if (u->sap_event) + m->core->mainloop->time_free(u->sap_event); if (u->source_output) { - pa_source_output_disconnect(u->source_output); + pa_source_output_unlink(u->source_output); pa_source_output_unref(u->source_output); } @@ -371,7 +388,8 @@ void pa__done(pa_core *c, pa_module*m) { pa_sap_send(&u->sap_context, 1); pa_sap_context_destroy(&u->sap_context); - pa_memblockq_free(u->memblockq); + if (u->memblockq) + pa_memblockq_free(u->memblockq); pa_xfree(u); } diff --git a/src/modules/rtp/rtp.c b/src/modules/rtp/rtp.c index 03a01412..997fcc34 100644 --- a/src/modules/rtp/rtp.c +++ b/src/modules/rtp/rtp.c @@ -25,7 +25,6 @@ #include <config.h> #endif -#include <assert.h> #include <fcntl.h> #include <stdlib.h> #include <string.h> @@ -40,12 +39,14 @@ #include <pulsecore/core-error.h> #include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> #include "rtp.h" pa_rtp_context* pa_rtp_context_init_send(pa_rtp_context *c, int fd, uint32_t ssrc, uint8_t payload, size_t frame_size) { - assert(c); - assert(fd >= 0); + pa_assert(c); + pa_assert(fd >= 0); c->fd = fd; c->sequence = (uint16_t) (rand()*rand()); @@ -63,11 +64,11 @@ int pa_rtp_send(pa_rtp_context *c, size_t size, pa_memblockq *q) { struct iovec iov[MAX_IOVECS]; pa_memblock* mb[MAX_IOVECS]; int iov_idx = 1; - size_t n = 0, skip = 0; + size_t n = 0; - assert(c); - assert(size > 0); - assert(q); + pa_assert(c); + pa_assert(size > 0); + pa_assert(q); if (pa_memblockq_get_length(q) < size) return 0; @@ -76,24 +77,26 @@ int pa_rtp_send(pa_rtp_context *c, size_t size, pa_memblockq *q) { int r; pa_memchunk chunk; + pa_memchunk_reset(&chunk); + if ((r = pa_memblockq_peek(q, &chunk)) >= 0) { size_t k = n + chunk.length > size ? size - n : chunk.length; - if (chunk.memblock) { - iov[iov_idx].iov_base = (void*)((uint8_t*) chunk.memblock->data + chunk.index); - iov[iov_idx].iov_len = k; - mb[iov_idx] = chunk.memblock; - iov_idx ++; + pa_assert(chunk.memblock); - n += k; - } + iov[iov_idx].iov_base = ((uint8_t*) pa_memblock_acquire(chunk.memblock) + chunk.index); + iov[iov_idx].iov_len = k; + mb[iov_idx] = chunk.memblock; + iov_idx ++; - skip += k; - pa_memblockq_drop(q, &chunk, k); + n += k; + pa_memblockq_drop(q, k); } - if (r < 0 || !chunk.memblock || n >= size || iov_idx >= MAX_IOVECS) { + pa_assert(n % c->frame_size == 0); + + if (r < 0 || n >= size || iov_idx >= MAX_IOVECS) { uint32_t header[3]; struct msghdr m; int k, i; @@ -116,17 +119,19 @@ int pa_rtp_send(pa_rtp_context *c, size_t size, pa_memblockq *q) { k = sendmsg(c->fd, &m, MSG_DONTWAIT); - for (i = 1; i < iov_idx; i++) + for (i = 1; i < iov_idx; i++) { + pa_memblock_release(mb[i]); pa_memblock_unref(mb[i]); + } c->sequence++; } else k = 0; - c->timestamp += skip/c->frame_size; + c->timestamp += n/c->frame_size; if (k < 0) { - if (errno != EAGAIN) /* If the queue is full, just ignore it */ + if (errno != EAGAIN && errno != EINTR) /* If the queue is full, just ignore it */ pa_log("sendmsg() failed: %s", pa_cstrerror(errno)); return -1; } @@ -135,7 +140,6 @@ int pa_rtp_send(pa_rtp_context *c, size_t size, pa_memblockq *q) { break; n = 0; - skip = 0; iov_idx = 1; } } @@ -144,7 +148,7 @@ int pa_rtp_send(pa_rtp_context *c, size_t size, pa_memblockq *q) { } pa_rtp_context* pa_rtp_context_init_recv(pa_rtp_context *c, int fd, size_t frame_size) { - assert(c); + pa_assert(c); c->fd = fd; c->frame_size = frame_size; @@ -159,13 +163,13 @@ int pa_rtp_recv(pa_rtp_context *c, pa_memchunk *chunk, pa_mempool *pool) { int cc; ssize_t r; - assert(c); - assert(chunk); + pa_assert(c); + pa_assert(chunk); - chunk->memblock = NULL; + pa_memchunk_reset(chunk); if (ioctl(c->fd, FIONREAD, &size) < 0) { - pa_log("FIONREAD failed: %s", pa_cstrerror(errno)); + pa_log_warn("FIONREAD failed: %s", pa_cstrerror(errno)); goto fail; } @@ -174,7 +178,7 @@ int pa_rtp_recv(pa_rtp_context *c, pa_memchunk *chunk, pa_mempool *pool) { chunk->memblock = pa_memblock_new(pool, size); - iov.iov_base = chunk->memblock->data; + iov.iov_base = pa_memblock_acquire(chunk->memblock); iov.iov_len = size; m.msg_name = NULL; @@ -185,36 +189,41 @@ int pa_rtp_recv(pa_rtp_context *c, pa_memchunk *chunk, pa_mempool *pool) { m.msg_controllen = 0; m.msg_flags = 0; - if ((r = recvmsg(c->fd, &m, 0)) != size) { - pa_log("recvmsg() failed: %s", r < 0 ? pa_cstrerror(errno) : "size mismatch"); + r = recvmsg(c->fd, &m, 0); + pa_memblock_release(chunk->memblock); + + if (r != size) { + if (r < 0 && errno != EAGAIN && errno != EINTR) + pa_log_warn("recvmsg() failed: %s", r < 0 ? pa_cstrerror(errno) : "size mismatch"); + goto fail; } if (size < 12) { - pa_log("RTP packet too short."); + pa_log_warn("RTP packet too short."); goto fail; } - memcpy(&header, chunk->memblock->data, sizeof(uint32_t)); - memcpy(&c->timestamp, (uint8_t*) chunk->memblock->data + 4, sizeof(uint32_t)); - memcpy(&c->ssrc, (uint8_t*) chunk->memblock->data + 8, sizeof(uint32_t)); + memcpy(&header, iov.iov_base, sizeof(uint32_t)); + memcpy(&c->timestamp, (uint8_t*) iov.iov_base + 4, sizeof(uint32_t)); + memcpy(&c->ssrc, (uint8_t*) iov.iov_base + 8, sizeof(uint32_t)); header = ntohl(header); c->timestamp = ntohl(c->timestamp); c->ssrc = ntohl(c->ssrc); if ((header >> 30) != 2) { - pa_log("Unsupported RTP version."); + pa_log_warn("Unsupported RTP version."); goto fail; } if ((header >> 29) & 1) { - pa_log("RTP padding not supported."); + pa_log_warn("RTP padding not supported."); goto fail; } if ((header >> 28) & 1) { - pa_log("RTP header extensions not supported."); + pa_log_warn("RTP header extensions not supported."); goto fail; } @@ -223,7 +232,7 @@ int pa_rtp_recv(pa_rtp_context *c, pa_memchunk *chunk, pa_mempool *pool) { c->sequence = header & 0xFFFF; if (12 + cc*4 > size) { - pa_log("RTP packet too short. (CSRC)"); + pa_log_warn("RTP packet too short. (CSRC)"); goto fail; } @@ -231,7 +240,7 @@ int pa_rtp_recv(pa_rtp_context *c, pa_memchunk *chunk, pa_mempool *pool) { chunk->length = size - chunk->index; if (chunk->length % c->frame_size != 0) { - pa_log("Vad RTP packet size."); + pa_log_warn("Bad RTP packet size."); goto fail; } @@ -245,7 +254,7 @@ fail: } uint8_t pa_rtp_payload_from_sample_spec(const pa_sample_spec *ss) { - assert(ss); + pa_assert(ss); if (ss->format == PA_SAMPLE_ULAW && ss->rate == 8000 && ss->channels == 1) return 0; @@ -260,7 +269,7 @@ uint8_t pa_rtp_payload_from_sample_spec(const pa_sample_spec *ss) { } pa_sample_spec *pa_rtp_sample_spec_from_payload(uint8_t payload, pa_sample_spec *ss) { - assert(ss); + pa_assert(ss); switch (payload) { case 0: @@ -295,17 +304,17 @@ pa_sample_spec *pa_rtp_sample_spec_from_payload(uint8_t payload, pa_sample_spec } pa_sample_spec *pa_rtp_sample_spec_fixup(pa_sample_spec * ss) { - assert(ss); + pa_assert(ss); if (!pa_rtp_sample_spec_valid(ss)) ss->format = PA_SAMPLE_S16BE; - assert(pa_rtp_sample_spec_valid(ss)); + pa_assert(pa_rtp_sample_spec_valid(ss)); return ss; } int pa_rtp_sample_spec_valid(const pa_sample_spec *ss) { - assert(ss); + pa_assert(ss); if (!pa_sample_spec_valid(ss)) return 0; @@ -318,9 +327,9 @@ int pa_rtp_sample_spec_valid(const pa_sample_spec *ss) { } void pa_rtp_context_destroy(pa_rtp_context *c) { - assert(c); + pa_assert(c); - close(c->fd); + pa_close(c->fd); } const char* pa_rtp_format_to_string(pa_sample_format_t f) { @@ -339,7 +348,7 @@ const char* pa_rtp_format_to_string(pa_sample_format_t f) { } pa_sample_format_t pa_rtp_string_to_format(const char *s) { - assert(s); + pa_assert(s); if (!(strcmp(s, "L16"))) return PA_SAMPLE_S16BE; diff --git a/src/modules/rtp/sap.c b/src/modules/rtp/sap.c index 82421807..ed7eb0be 100644 --- a/src/modules/rtp/sap.c +++ b/src/modules/rtp/sap.c @@ -25,7 +25,6 @@ #include <config.h> #endif -#include <assert.h> #include <time.h> #include <stdlib.h> #include <sys/types.h> @@ -46,6 +45,7 @@ #include <pulsecore/core-error.h> #include <pulsecore/core-util.h> #include <pulsecore/log.h> +#include <pulsecore/macro.h> #include "sap.h" #include "sdp.h" @@ -53,9 +53,9 @@ #define MIME_TYPE "application/sdp" pa_sap_context* pa_sap_context_init_send(pa_sap_context *c, int fd, char *sdp_data) { - assert(c); - assert(fd >= 0); - assert(sdp_data); + pa_assert(c); + pa_assert(fd >= 0); + pa_assert(sdp_data); c->fd = fd; c->sdp_data = sdp_data; @@ -65,9 +65,9 @@ pa_sap_context* pa_sap_context_init_send(pa_sap_context *c, int fd, char *sdp_da } void pa_sap_context_destroy(pa_sap_context *c) { - assert(c); + pa_assert(c); - close(c->fd); + pa_close(c->fd); pa_xfree(c->sdp_data); } @@ -85,7 +85,7 @@ int pa_sap_send(pa_sap_context *c, int goodbye) { return -1; } - assert(sa->sa_family == AF_INET || sa->sa_family == AF_INET6); + pa_assert(sa->sa_family == AF_INET || sa->sa_family == AF_INET6); header = htonl(((uint32_t) 1 << 29) | (sa->sa_family == AF_INET6 ? (uint32_t) 1 << 28 : 0) | @@ -113,14 +113,14 @@ int pa_sap_send(pa_sap_context *c, int goodbye) { m.msg_flags = 0; if ((k = sendmsg(c->fd, &m, MSG_DONTWAIT)) < 0) - pa_log("sendmsg() failed: %s\n", pa_cstrerror(errno)); + pa_log_warn("sendmsg() failed: %s\n", pa_cstrerror(errno)); return k; } pa_sap_context* pa_sap_context_init_recv(pa_sap_context *c, int fd) { - assert(c); - assert(fd >= 0); + pa_assert(c); + pa_assert(fd >= 0); c->fd = fd; c->sdp_data = NULL; @@ -136,11 +136,11 @@ int pa_sap_recv(pa_sap_context *c, int *goodbye) { int six, ac; ssize_t r; - assert(c); - assert(goodbye); + pa_assert(c); + pa_assert(goodbye); if (ioctl(c->fd, FIONREAD, &size) < 0) { - pa_log("FIONREAD failed: %s", pa_cstrerror(errno)); + pa_log_warn("FIONREAD failed: %s", pa_cstrerror(errno)); goto fail; } @@ -159,12 +159,12 @@ int pa_sap_recv(pa_sap_context *c, int *goodbye) { m.msg_flags = 0; if ((r = recvmsg(c->fd, &m, 0)) != size) { - pa_log("recvmsg() failed: %s", r < 0 ? pa_cstrerror(errno) : "size mismatch"); + pa_log_warn("recvmsg() failed: %s", r < 0 ? pa_cstrerror(errno) : "size mismatch"); goto fail; } if (size < 4) { - pa_log("SAP packet too short."); + pa_log_warn("SAP packet too short."); goto fail; } @@ -172,17 +172,17 @@ int pa_sap_recv(pa_sap_context *c, int *goodbye) { header = ntohl(header); if (header >> 29 != 1) { - pa_log("Unsupported SAP version."); + pa_log_warn("Unsupported SAP version."); goto fail; } if ((header >> 25) & 1) { - pa_log("Encrypted SAP not supported."); + pa_log_warn("Encrypted SAP not supported."); goto fail; } if ((header >> 24) & 1) { - pa_log("Compressed SAP not supported."); + pa_log_warn("Compressed SAP not supported."); goto fail; } @@ -191,7 +191,7 @@ int pa_sap_recv(pa_sap_context *c, int *goodbye) { k = 4 + (six ? 16 : 4) + ac*4; if (size < k) { - pa_log("SAP packet too short (AD)."); + pa_log_warn("SAP packet too short (AD)."); goto fail; } @@ -202,7 +202,7 @@ int pa_sap_recv(pa_sap_context *c, int *goodbye) { e += sizeof(MIME_TYPE); size -= sizeof(MIME_TYPE); } else if ((unsigned) size < sizeof(PA_SDP_HEADER)-1 || strncmp(e, PA_SDP_HEADER, sizeof(PA_SDP_HEADER)-1)) { - pa_log("Invalid SDP header."); + pa_log_warn("Invalid SDP header."); goto fail; } diff --git a/src/modules/rtp/sdp.c b/src/modules/rtp/sdp.c index 8b0bd535..50ac157a 100644 --- a/src/modules/rtp/sdp.c +++ b/src/modules/rtp/sdp.c @@ -25,7 +25,6 @@ #include <config.h> #endif -#include <assert.h> #include <time.h> #include <stdlib.h> #include <sys/types.h> @@ -35,36 +34,33 @@ #include <string.h> #include <pulse/xmalloc.h> +#include <pulse/util.h> #include <pulsecore/core-util.h> #include <pulsecore/log.h> +#include <pulsecore/macro.h> #include "sdp.h" #include "rtp.h" - char *pa_sdp_build(int af, const void *src, const void *dst, const char *name, uint16_t port, uint8_t payload, const pa_sample_spec *ss) { uint32_t ntp; - char buf_src[64], buf_dst[64]; + char buf_src[64], buf_dst[64], un[64]; const char *u, *f, *a; - assert(src); - assert(dst); - assert(af == AF_INET || af == AF_INET6); + pa_assert(src); + pa_assert(dst); + pa_assert(af == AF_INET || af == AF_INET6); - f = pa_rtp_format_to_string(ss->format); - assert(f); + pa_assert_se(f = pa_rtp_format_to_string(ss->format)); - if (!(u = getenv("USER"))) - if (!(u = getenv("USERNAME"))) - u = "-"; + if (!(u = pa_get_user_name(un, sizeof(un)))) + u = "-"; ntp = time(NULL) + 2208988800U; - a = inet_ntop(af, src, buf_src, sizeof(buf_src)); - assert(a); - a = inet_ntop(af, dst, buf_dst, sizeof(buf_dst)); - assert(a); + pa_assert_se(a = inet_ntop(af, src, buf_src, sizeof(buf_src))); + pa_assert_se(a = inet_ntop(af, dst, buf_dst, sizeof(buf_dst))); return pa_sprintf_malloc( PA_SDP_HEADER @@ -86,8 +82,8 @@ char *pa_sdp_build(int af, const void *src, const void *dst, const char *name, u static pa_sample_spec *parse_sdp_sample_spec(pa_sample_spec *ss, char *c) { unsigned rate, channels; - assert(ss); - assert(c); + pa_assert(ss); + pa_assert(c); if (pa_startswith(c, "L16/")) { ss->format = PA_SAMPLE_S16BE; @@ -123,8 +119,8 @@ pa_sdp_info *pa_sdp_parse(const char *t, pa_sdp_info *i, int is_goodbye) { uint16_t port = 0; int ss_valid = 0; - assert(t); - assert(i); + pa_assert(t); + pa_assert(i); i->origin = i->session_name = NULL; i->salen = 0; @@ -258,7 +254,7 @@ fail: } void pa_sdp_info_destroy(pa_sdp_info *i) { - assert(i); + pa_assert(i); pa_xfree(i->origin); pa_xfree(i->session_name); |