From 9324bcc3617caf76ef50a6eefb7e51b6d987a0b4 Mon Sep 17 00:00:00 2001 From: Mike Blumenkrantz Date: Tue, 17 Mar 2020 12:06:13 -0400 Subject: [PATCH] ecore/audio: rewrite pulseaudio output to not use global variables this breaks down immediately when calling init/shutdown in quick succession due to the async nature of pulseaudio. we have object-based private data, so we can just use that instead Reviewed-by: Marcel Hollerbach Differential Revision: https://phab.enlightenment.org/D11531 --- .../ecore_audio/ecore_audio_obj_out_pulse.c | 125 +++++++++++------- 1 file changed, 76 insertions(+), 49 deletions(-) diff --git a/src/lib/ecore_audio/ecore_audio_obj_out_pulse.c b/src/lib/ecore_audio/ecore_audio_obj_out_pulse.c index 5d61e0eb3e..edcc924acb 100644 --- a/src/lib/ecore_audio/ecore_audio_obj_out_pulse.c +++ b/src/lib/ecore_audio/ecore_audio_obj_out_pulse.c @@ -25,27 +25,19 @@ extern pa_mainloop_api functable; #define MY_CLASS ECORE_AUDIO_OUT_PULSE_CLASS #define MY_CLASS_NAME "Ecore_Audio_Out_Pulse" -struct _Ecore_Audio_Pulse_Class { - pa_mainloop_api *api; - pa_context *context; - pa_context_state_t state; - Ecore_Job *state_job; - Eina_List *outputs; -}; - -static struct _Ecore_Audio_Pulse_Class class_vars = { - .api = &functable, -}; - struct _Ecore_Audio_Out_Pulse_Data { - char *foo; + pa_mainloop_api *api; + pa_context *context; + pa_context_state_t state; + Ecore_Job *state_job; + Eina_List *outputs; }; typedef struct _Ecore_Audio_Out_Pulse_Data Ecore_Audio_Out_Pulse_Data; EOLIAN static void -_ecore_audio_out_pulse_ecore_audio_volume_set(Eo *eo_obj, Ecore_Audio_Out_Pulse_Data *_pd EINA_UNUSED, double volume) +_ecore_audio_out_pulse_ecore_audio_volume_set(Eo *eo_obj, Ecore_Audio_Out_Pulse_Data *pd, double volume) { Eo *in; pa_stream *stream = NULL; @@ -65,7 +57,7 @@ _ecore_audio_out_pulse_ecore_audio_volume_set(Eo *eo_obj, Ecore_Audio_Out_Pulse_ EINA_LIST_FOREACH(out_obj->inputs, input, in) { stream = efl_key_data_get(in, "pulse_data"); idx = EPA_CALL(pa_stream_get_index)(stream); - EPA_CALL(pa_operation_unref)(EPA_CALL(pa_context_set_sink_input_volume)(class_vars.context, idx, &pa_volume, NULL, NULL)); + EPA_CALL(pa_operation_unref)(EPA_CALL(pa_context_set_sink_input_volume)(pd->context, idx, &pa_volume, NULL, NULL)); } } @@ -114,6 +106,7 @@ static Eina_Bool _input_attach_internal(Eo *eo_obj, Eo *in) pa_stream *stream; Eina_Bool ret = EINA_FALSE; Ecore_Audio_Object *ea_obj = efl_data_scope_get(eo_obj, ECORE_AUDIO_CLASS); + Ecore_Audio_Out_Pulse_Data *pd = efl_data_scope_get(eo_obj, MY_CLASS); if (!EPA_LOAD()) return EINA_FALSE; ret = ecore_audio_obj_out_input_attach(efl_super(eo_obj, MY_CLASS), in); @@ -128,7 +121,7 @@ static Eina_Bool _input_attach_internal(Eo *eo_obj, Eo *in) ss.rate = ss.rate * speed; - stream = EPA_CALL(pa_stream_new)(class_vars.context, name, &ss, NULL); + stream = EPA_CALL(pa_stream_new)(pd->context, name, &ss, NULL); if (!stream) { ERR("Could not create stream"); ecore_audio_obj_out_input_detach(efl_super(eo_obj, MY_CLASS), in); @@ -151,18 +144,27 @@ static Eina_Bool _input_attach_internal(Eo *eo_obj, Eo *in) static void _delayed_attach_cb(void *data, const Efl_Event *event) { - Eo *in = data; - efl_event_callback_del(event->object, ECORE_AUDIO_OUT_PULSE_EVENT_CONTEXT_READY, _delayed_attach_cb, in); + efl_event_callback_del(event->object, ECORE_AUDIO_OUT_PULSE_EVENT_CONTEXT_READY, _delayed_attach_cb, data); - _input_attach_internal(event->object, in); + _input_attach_internal(event->object, data); +} + +static Eina_Bool +_is_input_attached(Eo *eo_obj, Eo *in) +{ + Ecore_Audio_Output *out_obj = efl_data_scope_get(eo_obj, ECORE_AUDIO_OUT_CLASS); + if (!out_obj->inputs) return EINA_FALSE; + return !!eina_list_data_find(out_obj->inputs, in); } EOLIAN static Eina_Bool -_ecore_audio_out_pulse_ecore_audio_out_input_attach(Eo *eo_obj, Ecore_Audio_Out_Pulse_Data *_pd EINA_UNUSED, Eo *in) +_ecore_audio_out_pulse_ecore_audio_out_input_attach(Eo *eo_obj, Ecore_Audio_Out_Pulse_Data *pd, Eo *in) { Eina_Bool retval = EINA_TRUE; - if (class_vars.state != PA_CONTEXT_READY) { + if (_is_input_attached(eo_obj, in)) return EINA_TRUE; + + if (pd->state != PA_CONTEXT_READY) { DBG("Delaying input_attach because PA context is not ready."); efl_event_callback_add(eo_obj, ECORE_AUDIO_OUT_PULSE_EVENT_CONTEXT_READY, _delayed_attach_cb, in); } else { @@ -180,16 +182,32 @@ static void _drain_cb(pa_stream *stream, int success EINA_UNUSED, void *data EIN } EOLIAN static Eina_Bool -_ecore_audio_out_pulse_ecore_audio_out_input_detach(Eo *eo_obj, Ecore_Audio_Out_Pulse_Data *_pd EINA_UNUSED, Eo *in) +_ecore_audio_out_pulse_ecore_audio_out_input_detach(Eo *eo_obj, Ecore_Audio_Out_Pulse_Data *pd, Eo *in) { pa_stream *stream = NULL; Eina_Bool ret2 = EINA_FALSE; pa_operation *op; - if (!EPA_LOAD()) return EINA_FALSE; + if (!EPA_LOAD()) + { + ERR("Failed to load PA!"); + return EINA_FALSE; + } + if (!_is_input_attached(eo_obj, in)) + { + ERR("Input object passed is not currently attached to this output!"); + return EINA_FALSE; + } + if (pd->state != PA_CONTEXT_READY) + efl_event_callback_del(in, ECORE_AUDIO_OUT_PULSE_EVENT_CONTEXT_READY, _delayed_attach_cb, pd); + else + efl_event_callback_del(in, ECORE_AUDIO_IN_EVENT_IN_SAMPLERATE_CHANGED, _update_samplerate_cb, eo_obj); ret2 = ecore_audio_obj_out_input_detach(efl_super(eo_obj, MY_CLASS), in); if (!ret2) - return EINA_FALSE; + { + ERR("Super call failed for ecore_audio_obj_out_input_detach!"); + return EINA_FALSE; + } stream = efl_key_data_get(in, "pulse_data"); @@ -197,7 +215,12 @@ _ecore_audio_out_pulse_ecore_audio_out_input_detach(Eo *eo_obj, Ecore_Audio_Out_ op = EPA_CALL(pa_stream_drain) (stream, _drain_cb, NULL); if (!op) { - ERR("Failed to drain PulseAudio stream."); + op = EPA_CALL(pa_stream_flush)(stream, _drain_cb, NULL); + if (!op) + { + EPA_CALL(pa_stream_disconnect)(stream); + EPA_CALL(pa_stream_unref)(stream); + } return EINA_FALSE; } @@ -205,67 +228,69 @@ _ecore_audio_out_pulse_ecore_audio_out_input_detach(Eo *eo_obj, Ecore_Audio_Out_ return EINA_TRUE; } -static void _state_cb(pa_context *context, void *data EINA_UNUSED) +static void _state_cb(pa_context *context, void *data) { Eina_List *out, *tmp; Eo *eo_obj; pa_context_state_t state; + Ecore_Audio_Out_Pulse_Data *pd = data; if (!EPA_LOAD()) return; state = EPA_CALL(pa_context_get_state)(context); - class_vars.state = state; + pd->state = state; //ref everything in the list to be sure... - EINA_LIST_FOREACH(class_vars.outputs, out, eo_obj) { + EINA_LIST_FOREACH(pd->outputs, out, eo_obj) { efl_ref(eo_obj); } // the callback here can delete things in the list.. if (state == PA_CONTEXT_READY) { DBG("PA context ready."); - EINA_LIST_FOREACH(class_vars.outputs, out, eo_obj) { + EINA_LIST_FOREACH(pd->outputs, out, eo_obj) { efl_event_callback_call(eo_obj, ECORE_AUDIO_OUT_PULSE_EVENT_CONTEXT_READY, NULL); } } else if ((state == PA_CONTEXT_FAILED) || (state == PA_CONTEXT_TERMINATED)) { DBG("PA context fail."); - EINA_LIST_FOREACH(class_vars.outputs, out, eo_obj) { + EINA_LIST_FOREACH(pd->outputs, out, eo_obj) { efl_event_callback_call(eo_obj, ECORE_AUDIO_OUT_PULSE_EVENT_CONTEXT_FAIL, NULL); } } else { DBG("Connection state %i", state); } // now unref everything safely - EINA_LIST_FOREACH_SAFE(class_vars.outputs, out, tmp, eo_obj) { + EINA_LIST_FOREACH_SAFE(pd->outputs, out, tmp, eo_obj) { efl_unref(eo_obj); } } -static void _state_job(void *data EINA_UNUSED) +static void _state_job(void *data) { - if ((class_vars.state == PA_CONTEXT_FAILED) || - (class_vars.state == PA_CONTEXT_TERMINATED)) + Ecore_Audio_Out_Pulse_Data *pd = data; + if ((pd->state == PA_CONTEXT_FAILED) || + (pd->state == PA_CONTEXT_TERMINATED)) { Eo *eo_obj; Eina_List *out, *tmp; DBG("PA context fail."); //ref everything in the list to be sure... - EINA_LIST_FOREACH(class_vars.outputs, out, eo_obj) { + EINA_LIST_FOREACH(pd->outputs, out, eo_obj) { efl_ref(eo_obj); } // the callback here can delete things in the list.. - EINA_LIST_FOREACH(class_vars.outputs, out, eo_obj) { + EINA_LIST_FOREACH(pd->outputs, out, eo_obj) { efl_event_callback_call(eo_obj, ECORE_AUDIO_OUT_PULSE_EVENT_CONTEXT_FAIL, NULL); } // now unref everything safely - EINA_LIST_FOREACH_SAFE(class_vars.outputs, out, tmp, eo_obj) { + EINA_LIST_FOREACH_SAFE(pd->outputs, out, tmp, eo_obj) { efl_unref(eo_obj); } } - class_vars.state_job = NULL; + pd->state_job = NULL; } EOLIAN static Eo * -_ecore_audio_out_pulse_efl_object_constructor(Eo *eo_obj, Ecore_Audio_Out_Pulse_Data *_pd EINA_UNUSED) +_ecore_audio_out_pulse_efl_object_constructor(Eo *eo_obj, Ecore_Audio_Out_Pulse_Data *pd) { int argc; char **argv, *disp = NULL; @@ -274,10 +299,11 @@ _ecore_audio_out_pulse_efl_object_constructor(Eo *eo_obj, Ecore_Audio_Out_Pulse_ if (!EPA_LOAD()) return NULL; eo_obj = efl_constructor(efl_super(eo_obj, MY_CLASS)); + pd->api = &functable; out_obj->need_writer = EINA_FALSE; - if (!class_vars.context) { + if (!pd->context) { // if we're in a wayland world rather than x11... but DISPLAY also set... if (getenv("WAYLAND_DISPLAY")) disp = getenv("DISPLAY"); @@ -310,9 +336,9 @@ _ecore_audio_out_pulse_efl_object_constructor(Eo *eo_obj, Ecore_Audio_Out_Pulse_ ecore_app_args_get(&argc, &argv); if (!argc) { DBG("Could not get program name, pulse outputs will be named ecore_audio"); - class_vars.context = EPA_CALL(pa_context_new)(class_vars.api, "ecore_audio"); + pd->context = EPA_CALL(pa_context_new)(pd->api, "ecore_audio"); } else { - class_vars.context = EPA_CALL(pa_context_new)(class_vars.api, basename(argv[0])); + pd->context = EPA_CALL(pa_context_new)(pd->api, basename(argv[0])); } // if we had a display value and a displayenv buffer then let's restore // the previous value content of DISPLAY as we duplicated it above and @@ -327,21 +353,22 @@ _ecore_audio_out_pulse_efl_object_constructor(Eo *eo_obj, Ecore_Audio_Out_Pulse_ // free up our temporary local DISPLAY env sring copy if we have it if (disp) free(disp); - EPA_CALL(pa_context_set_state_callback)(class_vars.context, _state_cb, NULL); - EPA_CALL(pa_context_connect)(class_vars.context, NULL, PA_CONTEXT_NOFLAGS, NULL); + EPA_CALL(pa_context_set_state_callback)(pd->context, _state_cb, pd); + EPA_CALL(pa_context_connect)(pd->context, NULL, PA_CONTEXT_NOFLAGS, NULL); } - class_vars.outputs = eina_list_append(class_vars.outputs, eo_obj); - if (class_vars.state_job) ecore_job_del(class_vars.state_job); - class_vars.state_job = ecore_job_add(_state_job, NULL); + pd->outputs = eina_list_append(pd->outputs, eo_obj); + pd->state_job = ecore_job_add(_state_job, pd); return eo_obj; } EOLIAN static void -_ecore_audio_out_pulse_efl_object_destructor(Eo *eo_obj, Ecore_Audio_Out_Pulse_Data *_pd EINA_UNUSED) +_ecore_audio_out_pulse_efl_object_destructor(Eo *eo_obj, Ecore_Audio_Out_Pulse_Data *pd) { - class_vars.outputs = eina_list_remove(class_vars.outputs, eo_obj); + pd->outputs = eina_list_remove(pd->outputs, eo_obj); + ecore_job_del(pd->state_job); + EPA_CALL(pa_context_unref)(pd->context); efl_destructor(efl_super(eo_obj, MY_CLASS)); }