From 9293581c62375c3a94c25eaf3c6f601414f05381 Mon Sep 17 00:00:00 2001 From: Daniel Willmann Date: Fri, 14 Dec 2012 23:38:08 +0000 Subject: [PATCH] ecore_audio: Add support for PulseAudio Only outputs are supported at the moment Signed-off-by: Daniel Willmann SVN revision: 80995 --- src/lib/ecore_audio/ecore_audio.c | 10 + src/lib/ecore_audio/ecore_audio_private.h | 23 + src/lib/ecore_audio/ecore_audio_pulse.c | 570 ++++++++++++++++++++++ 3 files changed, 603 insertions(+) create mode 100644 src/lib/ecore_audio/ecore_audio_pulse.c diff --git a/src/lib/ecore_audio/ecore_audio.c b/src/lib/ecore_audio/ecore_audio.c index ccd8e5b3d1..262b697b9d 100644 --- a/src/lib/ecore_audio/ecore_audio.c +++ b/src/lib/ecore_audio/ecore_audio.c @@ -76,6 +76,12 @@ ecore_audio_init(void) ECORE_AUDIO_OUTPUT_INPUT_ADDED = ecore_event_type_new(); ECORE_AUDIO_OUTPUT_INPUT_REMOVED = ecore_event_type_new(); +#ifdef HAVE_PULSE + mod = ecore_audio_pulse_init(); + if (mod) + ecore_audio_modules = eina_list_append(ecore_audio_modules, mod); +#endif + return _ecore_audio_init_count; } @@ -88,6 +94,10 @@ ecore_audio_shutdown(void) /* FIXME: Shutdown all the inputs and outputs first */ +#ifdef HAVE_PULSE + ecore_audio_pulse_shutdown(); +#endif + eina_list_free(ecore_audio_modules); eina_log_domain_unregister(_ecore_audio_log_dom); diff --git a/src/lib/ecore_audio/ecore_audio_private.h b/src/lib/ecore_audio/ecore_audio_private.h index a893aac50a..6f9b7603f1 100644 --- a/src/lib/ecore_audio/ecore_audio_private.h +++ b/src/lib/ecore_audio/ecore_audio_private.h @@ -9,6 +9,10 @@ #include #endif +#ifdef HAVE_PULSE +#include +#endif + #include #include @@ -289,6 +293,25 @@ struct _Ecore_Audio_Callback { extern Eina_List *ecore_audio_modules; +#ifdef HAVE_PULSE +/* PA mainloop integration */ +struct _Ecore_Audio_Pa_Private +{ + pa_mainloop_api api; + pa_context *context; + pa_context_state_t state; +}; + +/* ecore_audio_pulse */ +struct _Ecore_Audio_Pulse +{ + pa_stream *stream; +}; + +Ecore_Audio_Module *ecore_audio_pulse_init(void); +void ecore_audio_pulse_shutdown(void); +#endif /* HAVE_PULSE */ + /** * @} */ diff --git a/src/lib/ecore_audio/ecore_audio_pulse.c b/src/lib/ecore_audio/ecore_audio_pulse.c new file mode 100644 index 0000000000..3c85d16468 --- /dev/null +++ b/src/lib/ecore_audio/ecore_audio_pulse.c @@ -0,0 +1,570 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +#ifdef HAVE_PULSE +#include "Ecore.h" +#include "ecore_private.h" +#include "Ecore_Audio.h" +#include "ecore_audio_private.h" + +#include + +#include +#include +#include + +int _ecore_audio_pa_log_dom = -1; +static Ecore_Audio_Module *pulse_module = NULL; + +/* Ecore mainloop integration start */ +struct pa_io_event +{ + struct _Ecore_Audio_Pa_Private *mainloop; + Ecore_Fd_Handler *handler; + + void *userdata; + + pa_io_event_flags_t flags; + pa_io_event_cb_t callback; + pa_io_event_destroy_cb_t destroy_callback; +}; + +static Ecore_Fd_Handler_Flags +map_flags_to_ecore(pa_io_event_flags_t flags) +{ + return (Ecore_Fd_Handler_Flags)((flags & PA_IO_EVENT_INPUT ? ECORE_FD_READ : 0) | (flags & PA_IO_EVENT_OUTPUT ? ECORE_FD_WRITE : 0) | (flags & PA_IO_EVENT_ERROR ? ECORE_FD_ERROR : 0) | (flags & PA_IO_EVENT_HANGUP ? ECORE_FD_READ : 0)); +} + +static Eina_Bool +_ecore_io_wrapper(void *data, Ecore_Fd_Handler *handler) +{ + char buf[64]; + pa_io_event_flags_t flags = 0; + pa_io_event *event = (pa_io_event *)data; + + if (ecore_main_fd_handler_active_get(handler, ECORE_FD_READ)) + { + flags |= PA_IO_EVENT_INPUT; + + /* Check for HUP and report */ + if (recv(ecore_main_fd_handler_fd_get(handler), buf, 64, MSG_PEEK)) + { + if (errno == ESHUTDOWN || errno == ECONNRESET || errno == ECONNABORTED || errno == ENETRESET) + { + DBG("HUP condition detected"); + flags |= PA_IO_EVENT_HANGUP; + } + } + } + + if (ecore_main_fd_handler_active_get(handler, ECORE_FD_WRITE)) + flags |= PA_IO_EVENT_OUTPUT; + if (ecore_main_fd_handler_active_get(handler, ECORE_FD_ERROR)) + flags |= PA_IO_EVENT_ERROR; + + event->callback(&event->mainloop->api, event, ecore_main_fd_handler_fd_get(handler), flags, event->userdata); + + return ECORE_CALLBACK_RENEW; +} + +static pa_io_event * +_ecore_pa_io_new(pa_mainloop_api *api, int fd, pa_io_event_flags_t flags, pa_io_event_cb_t cb, void *userdata) +{ + pa_io_event *event; + struct _Ecore_Audio_Pa_Private *mloop; + + mloop = api->userdata; + + event = calloc(1, sizeof(pa_io_event)); + event->mainloop = mloop; + event->userdata = userdata; + event->callback = cb; + event->flags = flags; + event->handler = ecore_main_fd_handler_add(fd, map_flags_to_ecore(flags), _ecore_io_wrapper, event, NULL, NULL); + + return event; +} + +static void +_ecore_pa_io_enable(pa_io_event *event, pa_io_event_flags_t flags) +{ + event->flags = flags; + ecore_main_fd_handler_active_set(event->handler, map_flags_to_ecore(flags)); +} + +static void +_ecore_pa_io_free(pa_io_event *event) +{ + ecore_main_fd_handler_del(event->handler); + free(event); +} + +static void +_ecore_pa_io_set_destroy(pa_io_event *event, pa_io_event_destroy_cb_t cb) +{ + event->destroy_callback = cb; +} + +/* Timed events */ +struct pa_time_event +{ + struct _Ecore_Audio_Pa_Private *mainloop; + Ecore_Timer *timer; + struct timeval tv; + + void *userdata; + + pa_time_event_cb_t callback; + pa_time_event_destroy_cb_t destroy_callback; +}; + +Eina_Bool +_ecore_time_wrapper(void *data) +{ + pa_time_event *event = (pa_time_event *)data; + + event->callback(&event->mainloop->api, event, &event->tv, event->userdata); + + return ECORE_CALLBACK_CANCEL; +} + +pa_time_event * +_ecore_pa_time_new(pa_mainloop_api *api, const struct timeval *tv, pa_time_event_cb_t cb, void *userdata) +{ + pa_time_event *event; + struct _Ecore_Audio_Pa_Private *mloop; + struct timeval now; + double interval; + + mloop = api->userdata; + + event = calloc(1, sizeof(pa_time_event)); + event->mainloop = mloop; + event->userdata = userdata; + event->callback = cb; + event->tv = *tv; + + if (gettimeofday(&now, NULL) == -1) + { + ERR("Failed to get the current time!"); + return NULL; + } + + interval = (tv->tv_sec - now.tv_sec) + (tv->tv_usec - now.tv_usec) / 1000; + event->timer = ecore_timer_add(interval, _ecore_time_wrapper, event); + + return event; +} + +void +_ecore_pa_time_restart(pa_time_event *event, const struct timeval *tv) +{ + struct timeval now; + double interval; + + /* If tv is NULL disable timer */ + if (!tv) + { + ecore_timer_del(event->timer); + event->timer = NULL; + return; + } + + event->tv = *tv; + + if (gettimeofday(&now, NULL) == -1) + { + ERR("Failed to get the current time!"); + return; + } + + interval = (tv->tv_sec - now.tv_sec) + (tv->tv_usec - now.tv_usec) / 1000; + if (event->timer) + { + event->timer = ecore_timer_add(interval, _ecore_time_wrapper, event); + } + else + { + ecore_timer_interval_set(event->timer, interval); + ecore_timer_reset(event->timer); + } +} + +void +_ecore_pa_time_free(pa_time_event *event) +{ + if (event->timer) + ecore_timer_del(event->timer); + + event->timer = NULL; + + free(event); +} + +void +_ecore_pa_time_set_destroy(pa_time_event *event, pa_time_event_destroy_cb_t cb) +{ + event->destroy_callback = cb; +} + +/* Deferred events */ +struct pa_defer_event +{ + struct _Ecore_Audio_Pa_Private *mainloop; + Ecore_Idler *idler; + + void *userdata; + + pa_defer_event_cb_t callback; + pa_defer_event_destroy_cb_t destroy_callback; +}; + +Eina_Bool +_ecore_defer_wrapper(void *data) +{ + pa_defer_event *event = (pa_defer_event *)data; + + event->idler = NULL; + event->callback(&event->mainloop->api, event, event->userdata); + + return ECORE_CALLBACK_CANCEL; +} + +pa_defer_event * +_ecore_pa_defer_new(pa_mainloop_api *api, pa_defer_event_cb_t cb, void *userdata) +{ + pa_defer_event *event; + struct _Ecore_Audio_Pa_Private *mloop; + + mloop = api->userdata; + + event = calloc(1, sizeof(pa_defer_event)); + event->mainloop = mloop; + event->userdata = userdata; + event->callback = cb; + + event->idler = ecore_idler_add(_ecore_defer_wrapper, event); + + return event; +} + +void +_ecore_pa_defer_enable(pa_defer_event *event, int b) +{ + if (!b && event->idler) + { + ecore_idler_del(event->idler); + event->idler = NULL; + } + else if (b && !event->idler) + { + event->idler = ecore_idler_add(_ecore_defer_wrapper, event); + } +} + +void +_ecore_pa_defer_free(pa_defer_event *event) +{ + if (event->idler) + ecore_idler_del(event->idler); + + event->idler = NULL; + + free(event); +} + +void +_ecore_pa_defer_set_destroy(pa_defer_event *event, pa_defer_event_destroy_cb_t cb) +{ + event->destroy_callback = cb; +} + +static void +_ecore_pa_quit(pa_mainloop_api *api EINA_UNUSED, int retval EINA_UNUSED) +{ + /* FIXME: Need to clean up timers, etc.? */ + WRN("Not quitting mainloop, although PA requested it"); +} + +/* Function table for PA mainloop integration */ +static const pa_mainloop_api functable = { + .userdata = NULL, + + .io_new = _ecore_pa_io_new, + .io_enable = _ecore_pa_io_enable, + .io_free = _ecore_pa_io_free, + .io_set_destroy = _ecore_pa_io_set_destroy, + + .time_new = _ecore_pa_time_new, + .time_restart = _ecore_pa_time_restart, + .time_free = _ecore_pa_time_free, + .time_set_destroy = _ecore_pa_time_set_destroy, + + .defer_new = _ecore_pa_defer_new, + .defer_enable = _ecore_pa_defer_enable, + .defer_free = _ecore_pa_defer_free, + .defer_set_destroy = _ecore_pa_defer_set_destroy, + + .quit = _ecore_pa_quit, +}; + +/* ***************************************************** + * Ecore mainloop integration end + */ + +static Ecore_Audio_Object * +_pulse_input_new(Ecore_Audio_Object *input) +{ + Ecore_Audio_Input *in = (Ecore_Audio_Input *)input; + return (Ecore_Audio_Object *)in; +} + +static Ecore_Audio_Object * +_pulse_output_new(Ecore_Audio_Object *output) +{ + Ecore_Audio_Output *out = (Ecore_Audio_Output *)output; + struct _Ecore_Audio_Pulse *pulse; + + pulse = calloc(1, sizeof(struct _Ecore_Audio_Pulse)); + if (!pulse) + { + ERR("Could not allocate memory for private structure."); + free(out); + return NULL; + } + + out->module_data = pulse; + + return (Ecore_Audio_Object *)out; +} + +static void +_pulse_output_del(Ecore_Audio_Object *output) +{ + Ecore_Audio_Output *out = (Ecore_Audio_Output *)output; + free(out->module_data); +} + +static void +_pulse_output_volume_set(Ecore_Audio_Object *output, double vol) +{ + Ecore_Audio_Output *out = (Ecore_Audio_Output *)output; + struct _Ecore_Audio_Pa_Private *priv = (struct _Ecore_Audio_Pa_Private *)out->module->priv; + Eina_List *input; + Ecore_Audio_Input *in; + pa_stream *stream; + uint32_t idx; + pa_cvolume volume; + pa_operation *op; + + if (vol < 0) + vol = 0; + + pa_cvolume_set(&volume, 2, vol * PA_VOLUME_NORM); + + EINA_LIST_FOREACH(out->inputs, input, in) + { + stream = in->obj_data; + idx = pa_stream_get_index(stream); + op = pa_context_set_sink_input_volume(priv->context, idx, &volume, NULL, NULL); + pa_operation_unref(op); + } +} + +static void +_pulse_output_write_cb(pa_stream *stream, size_t len, void *data) +{ + Ecore_Audio_Input *in = (Ecore_Audio_Input *)data; + + void *buf; + int bread; + + buf = malloc(len); + + bread = ecore_audio_input_read((Ecore_Audio_Object *)in, buf, len); + pa_stream_write(stream, buf, bread, free, 0, PA_SEEK_RELATIVE); + if (bread < len && !in->ended) + { + in->ended = EINA_TRUE; + pa_operation_unref(pa_stream_drain(stream, NULL, NULL)); + } +} + +static Eina_Bool +_pulse_output_add_input(Ecore_Audio_Object *output, Ecore_Audio_Object *input) +{ + Ecore_Audio_Output *out = (Ecore_Audio_Output *)output; + Ecore_Audio_Input *in = (Ecore_Audio_Input *)input; + Ecore_Audio_Module *outmod = out->module; + struct _Ecore_Audio_Pa_Private *priv = (struct _Ecore_Audio_Pa_Private *)outmod->priv; + pa_stream *stream; + + pa_sample_spec ss = { + .format = PA_SAMPLE_FLOAT32LE, + .rate = in->samplerate, + .channels = in->channels, + }; + + stream = pa_stream_new(priv->context, in->name, &ss, NULL); + if (!stream) + { + ERR("Could not create stream"); + return EINA_FALSE; + } + + in->obj_data = stream; + + pa_stream_set_write_callback(stream, _pulse_output_write_cb, in); + pa_stream_connect_playback(stream, NULL, NULL, PA_STREAM_NOFLAGS, NULL, NULL); + + return EINA_TRUE; +} + +static void +_pulse_drain_cb(pa_stream *stream, int success, void *data) +{ + // XXX: Check success? + pa_stream_disconnect(stream); + pa_stream_unref(stream); +} + +static Eina_Bool +_pulse_output_del_input(Ecore_Audio_Object *output, Ecore_Audio_Object *input) +{ + Ecore_Audio_Input *in = (Ecore_Audio_Input *)input; + + pa_stream *stream = (pa_stream *)in->obj_data; + in->obj_data = NULL; + + pa_stream_set_write_callback(stream, NULL, NULL); + pa_operation_unref(pa_stream_drain(stream, _pulse_drain_cb, in)); + + return EINA_TRUE; +} + +static void +_pulse_output_update_input_format(Ecore_Audio_Object *output, Ecore_Audio_Object *input) +{ + Ecore_Audio_Input *in = (Ecore_Audio_Input *)input; + pa_stream *stream = (pa_stream *)in->obj_data; + + pa_operation_unref(pa_stream_update_sample_rate(stream, in->samplerate, NULL, NULL)); +} + +static int +_pulse_input_read(Ecore_Audio_Object *input, void *data, int len) +{ + // XXX: Implement + Ecore_Audio_Input *in = (Ecore_Audio_Input *)input; + + return 0; +} + +static void +_ecore_pa_state_cb(pa_context *context, void *data) +{ + struct _Ecore_Audio_Pa_Private *priv = (struct _Ecore_Audio_Pa_Private *)data; + pa_context_state_t state; + + state = pa_context_get_state(context); + + if (state == PA_CONTEXT_READY) + { + DBG("PA context connected."); + } + else + { + DBG("Connection state %i", state); + } + priv->state = state; +} + +static struct input_api inops = { + .input_new = _pulse_input_new, + .input_read = _pulse_input_read, +}; + +static struct output_api outops = { + .output_new = _pulse_output_new, + .output_del = _pulse_output_del, + .output_volume_set = _pulse_output_volume_set, + .output_add_input = _pulse_output_add_input, + .output_del_input = _pulse_output_del_input, + .output_update_input_format = _pulse_output_update_input_format, +}; + +/* externally accessible functions */ + +/** + * @brief Initialize the Ecore_Audio PA module + * + * @return the initialized module on success, NULL on error + */ +Ecore_Audio_Module * +ecore_audio_pulse_init(void) +{ + struct _Ecore_Audio_Pa_Private *priv; + + pulse_module = calloc(1, sizeof(Ecore_Audio_Module)); + if (!pulse_module) + { + ERR("Could not allocate memory for module."); + return NULL; + } + + priv = calloc(1, sizeof(struct _Ecore_Audio_Pa_Private)); + if (!priv) + { + ERR("Could not allocate memory for private module region."); + free(pulse_module); + return NULL; + } + + priv->api = functable; + priv->api.userdata = priv; + /* FIXME: Get name from application */ + priv->context = pa_context_new(&priv->api, "ecore_audio"); + if (!priv->context) + { + ERR("Could not create PulseAudio context."); + free(priv); + free(pulse_module); + return NULL; + } + + pa_context_set_state_callback(priv->context, _ecore_pa_state_cb, priv); + pa_context_connect(priv->context, NULL, PA_CONTEXT_NOFLAGS, NULL); + + ECORE_MAGIC_SET(pulse_module, ECORE_MAGIC_AUDIO_MODULE); + pulse_module->type = ECORE_AUDIO_TYPE_PULSE; + pulse_module->name = "pulse"; + pulse_module->priv = priv; + pulse_module->inputs = NULL; + pulse_module->outputs = NULL; + pulse_module->in_ops = &inops; + pulse_module->out_ops = &outops; + + return pulse_module; +} + +/** + * @brief Shut down the Ecore_Audio PA module + */ +void +ecore_audio_pulse_shutdown(void) +{ + struct _Ecore_Audio_Pa_Private *priv = (struct _Ecore_Audio_Pa_Private *)pulse_module->priv; + + /* XXX: Make sure all pending events are freed */ + priv->api.userdata = NULL; + free(priv); + free(pulse_module); + pulse_module = NULL; +} + +/** + * @} + */ + +#endif /* HAVE_PULSE */