diff --git a/configure.ac b/configure.ac index 2727448cf1..b2c35e86da 100644 --- a/configure.ac +++ b/configure.ac @@ -769,6 +769,18 @@ dnl *** pulseaudio *** translit(dnm, m, l) AM_CONDITIONAL(USE_PULSE, true) AG_GST_CHECK_FEATURE(PULSE, [pulseaudio plug-in], pulseaudio, [ AG_GST_PKG_CHECK_MODULES(PULSE, libpulse >= 0.9.8) + AG_GST_PKG_CHECK_MODULES(PULSE_0_9_11, libpulse >= 0.9.11) + if test x$HAVE_PULSE_0_9_11 = xyes; then + AC_DEFINE(HAVE_PULSE_0_9_11, 1, [defined if pulseaudio >= 0.9.11 is available]) + fi + AG_GST_PKG_CHECK_MODULES(PULSE_0_9_12, libpulse >= 0.9.12) + if test x$HAVE_PULSE_0_9_12 = xyes; then + AC_DEFINE(HAVE_PULSE_0_9_12, 1, [defined if pulseaudio >= 0.9.12 is available]) + fi + AG_GST_PKG_CHECK_MODULES(PULSE_0_9_13, libpulse >= 0.9.13) + if test x$HAVE_PULSE_0_9_13 = xyes; then + AC_DEFINE(HAVE_PULSE_0_9_13, 1, [defined if pulseaudio >= 0.9.13 is available]) + fi ]) dnl *** dv1394 *** diff --git a/ext/pulse/pulseprobe.c b/ext/pulse/pulseprobe.c index 59a250bec3..588a9c3941 100644 --- a/ext/pulse/pulseprobe.c +++ b/ext/pulse/pulseprobe.c @@ -92,7 +92,7 @@ gst_pulseprobe_invalidate (GstPulseProbe * c) g_list_foreach (c->devices, (GFunc) g_free, NULL); g_list_free (c->devices); c->devices = NULL; - c->devices_valid = 0; + c->devices_valid = FALSE; } static gboolean @@ -126,13 +126,22 @@ gst_pulseprobe_open (GstPulseProbe * c) goto unlock_and_fail; } - /* Wait until the context is ready */ - pa_threaded_mainloop_wait (c->mainloop); + for (;;) { + pa_context_state_t state; - if (pa_context_get_state (c->context) != PA_CONTEXT_READY) { - GST_WARNING_OBJECT (c->object, "Failed to connect context: %s", - pa_strerror (pa_context_errno (c->context))); - goto unlock_and_fail; + state = pa_context_get_state (c->context); + + if (!PA_CONTEXT_IS_GOOD (state)) { + GST_WARNING_OBJECT (c->object, "Failed to connect context: %s", + pa_strerror (pa_context_errno (c->context))); + goto unlock_and_fail; + } + + if (state == PA_CONTEXT_READY) + break; + + /* Wait until the context is ready */ + pa_threaded_mainloop_wait (c->mainloop); } pa_threaded_mainloop_unlock (c->mainloop); @@ -152,12 +161,23 @@ unlock_and_fail: return FALSE; } -#define CHECK_DEAD_GOTO(c, label) do { \ -if (!(c)->context || pa_context_get_state((c)->context) != PA_CONTEXT_READY) { \ - GST_WARNING_OBJECT((c)->object, "Not connected: %s", (c)->context ? pa_strerror(pa_context_errno((c)->context)) : "NULL"); \ - goto label; \ -} \ -} while(0); +static gboolean +gst_pulseprobe_is_dead (GstPulseProbe * pulseprobe) +{ + + if (!pulseprobe->context || + !PA_CONTEXT_IS_GOOD (pa_context_get_state (pulseprobe->context))) { + const gchar *err_str = + pulseprobe->context ? + pa_strerror (pa_context_errno (pulseprobe->context)) : NULL; + + GST_ELEMENT_ERROR ((pulseprobe), RESOURCE, FAILED, + ("Disconnected: %s", err_str), (NULL)); + return TRUE; + } + + return FALSE; +} static gboolean gst_pulseprobe_enumerate (GstPulseProbe * c) @@ -178,9 +198,13 @@ gst_pulseprobe_enumerate (GstPulseProbe * c) } c->operation_success = 0; - while (pa_operation_get_state (o) != PA_OPERATION_DONE) { + + while (pa_operation_get_state (o) == PA_OPERATION_RUNNING) { + + if (gst_pulseprobe_is_dead (c)) + goto unlock_and_fail; + pa_threaded_mainloop_wait (c->mainloop); - CHECK_DEAD_GOTO (c, unlock_and_fail); } if (!c->operation_success) { @@ -205,9 +229,12 @@ gst_pulseprobe_enumerate (GstPulseProbe * c) } c->operation_success = 0; - while (pa_operation_get_state (o) != PA_OPERATION_DONE) { + while (pa_operation_get_state (o) == PA_OPERATION_RUNNING) { + + if (gst_pulseprobe_is_dead (c)) + goto unlock_and_fail; + pa_threaded_mainloop_wait (c->mainloop); - CHECK_DEAD_GOTO (c, unlock_and_fail); } if (!c->operation_success) { @@ -220,7 +247,7 @@ gst_pulseprobe_enumerate (GstPulseProbe * c) o = NULL; } - c->devices_valid = 1; + c->devices_valid = TRUE; pa_threaded_mainloop_unlock (c->mainloop); @@ -246,6 +273,7 @@ gst_pulseprobe_close (GstPulseProbe * c) if (c->context) { pa_context_disconnect (c->context); + pa_context_set_state_callback (c->context, NULL, NULL); pa_context_unref (c->context); c->context = NULL; } @@ -257,8 +285,8 @@ gst_pulseprobe_close (GstPulseProbe * c) } GstPulseProbe * -gst_pulseprobe_new (GObject * object, GObjectClass * klass, guint prop_id, - const gchar * server, gboolean sinks, gboolean sources) +gst_pulseprobe_new (GObject * object, GObjectClass * klass, + guint prop_id, const gchar * server, gboolean sinks, gboolean sources) { GstPulseProbe *c = NULL; @@ -335,7 +363,9 @@ gst_pulseprobe_get_values (GstPulseProbe * c, guint prop_id, const GParamSpec * pspec) { GValueArray *array; - GValue value = { 0 }; + GValue value = { + 0 + }; GList *item; if (prop_id != c->prop_id) { diff --git a/ext/pulse/pulseprobe.h b/ext/pulse/pulseprobe.h index 3baf6db655..bd20591a01 100644 --- a/ext/pulse/pulseprobe.h +++ b/ext/pulse/pulseprobe.h @@ -37,7 +37,7 @@ struct _GstPulseProbe GObject *object; gchar *server; GList *devices; - int devices_valid; + gboolean devices_valid; pa_threaded_mainloop *mainloop; pa_context *context; diff --git a/ext/pulse/pulsesink.c b/ext/pulse/pulsesink.c index b41dba50c9..75ac84de0d 100644 --- a/ext/pulse/pulsesink.c +++ b/ext/pulse/pulsesink.c @@ -57,7 +57,6 @@ GST_DEBUG_CATEGORY_EXTERN (pulse_debug); * http://www.pulseaudio.org/ticket/314 * we need pulse-0.9.12 to use sink volume properties */ -/*#define HAVE_PULSE_0_9_12 */ enum { @@ -77,8 +76,6 @@ static void gst_pulsesink_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); static void gst_pulsesink_finalize (GObject * object); -static void gst_pulsesink_dispose (GObject * object); - static gboolean gst_pulsesink_open (GstAudioSink * asink); static gboolean gst_pulsesink_close (GstAudioSink * asink); @@ -161,30 +158,30 @@ gst_pulsesink_base_init (gpointer g_class) "width = (int) 16, " "depth = (int) 16, " "rate = (int) [ 1, MAX ], " - "channels = (int) [ 1, 16 ];" + "channels = (int) [ 1, 32 ];" "audio/x-raw-float, " "endianness = (int) { " ENDIANNESS " }, " "width = (int) 32, " "rate = (int) [ 1, MAX ], " - "channels = (int) [ 1, 16 ];" + "channels = (int) [ 1, 32 ];" "audio/x-raw-int, " "endianness = (int) { " ENDIANNESS " }, " "signed = (boolean) TRUE, " "width = (int) 32, " "depth = (int) 32, " "rate = (int) [ 1, MAX ], " - "channels = (int) [ 1, 16 ];" + "channels = (int) [ 1, 32 ];" "audio/x-raw-int, " "signed = (boolean) FALSE, " "width = (int) 8, " "depth = (int) 8, " "rate = (int) [ 1, MAX ], " - "channels = (int) [ 1, 16 ];" + "channels = (int) [ 1, 32 ];" "audio/x-alaw, " "rate = (int) [ 1, MAX], " - "channels = (int) [ 1, 16 ];" + "channels = (int) [ 1, 32 ];" "audio/x-mulaw, " - "rate = (int) [ 1, MAX], " "channels = (int) [ 1, 16 ]") + "rate = (int) [ 1, MAX], " "channels = (int) [ 1, 32 ]") ); GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); @@ -204,7 +201,6 @@ gst_pulsesink_class_init (GstPulseSinkClass * klass) GstBaseSinkClass *gstbasesink_class = GST_BASE_SINK_CLASS (klass); GstAudioSinkClass *gstaudiosink_class = GST_AUDIO_SINK_CLASS (klass); - gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_pulsesink_dispose); gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_pulsesink_finalize); gobject_class->set_property = GST_DEBUG_FUNCPTR (gst_pulsesink_set_property); gobject_class->get_property = GST_DEBUG_FUNCPTR (gst_pulsesink_get_property); @@ -237,11 +233,11 @@ gst_pulsesink_class_init (GstPulseSinkClass * klass) g_param_spec_string ("device-name", "Device name", "Human-readable name of the sound device", NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); -#ifdef HAVE_PULSE_0_9_12 +#if HAVE_PULSE_0_9_12 g_object_class_install_property (gobject_class, PROP_VOLUME, g_param_spec_double ("volume", "Volume", - "Volume of this stream", 0.0, 10.0, 1.0, + "Volume of this stream", 0.0, 1000.0, 1.0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); #endif } @@ -251,12 +247,26 @@ gst_pulsesink_init (GstPulseSink * pulsesink, GstPulseSinkClass * klass) { int e; - pulsesink->server = pulsesink->device = pulsesink->stream_name = NULL; + pulsesink->server = pulsesink->device = pulsesink->stream_name = + pulsesink->device_description = NULL; pulsesink->context = NULL; pulsesink->stream = NULL; - pulsesink->stream_mutex = g_mutex_new (); + pulsesink->volume = 1.0; + pulsesink->volume_set = FALSE; + +#if HAVE_PULSE_0_9_13 + pa_sample_spec_init (&pulsesink->sample_spec); +#else + pulsesink->sample_spec.format = PA_SAMPLE_INVALID; + pulsesink->sample_spec.rate = 0; + pulsesink->sample_spec.channels = 0; +#endif + + pulsesink->operation_success = FALSE; + pulsesink->did_reset = FALSE; + pulsesink->in_write = FALSE; pulsesink->mainloop = pa_threaded_mainloop_new (); g_assert (pulsesink->mainloop); @@ -265,31 +275,43 @@ gst_pulsesink_init (GstPulseSink * pulsesink, GstPulseSinkClass * klass) g_assert (e == 0); pulsesink->probe = gst_pulseprobe_new (G_OBJECT (pulsesink), G_OBJECT_GET_CLASS (pulsesink), PROP_DEVICE, pulsesink->device, TRUE, FALSE); /* TRUE for sinks, FALSE for sources */ - pulsesink->mixer = NULL; } static void gst_pulsesink_destroy_stream (GstPulseSink * pulsesink) { - g_mutex_lock (pulsesink->stream_mutex); if (pulsesink->stream) { pa_stream_disconnect (pulsesink->stream); + + /* Make sure we don't get any further callbacks */ + pa_stream_set_state_callback (pulsesink->stream, NULL, NULL); + pa_stream_set_write_callback (pulsesink->stream, NULL, NULL); + pa_stream_set_latency_update_callback (pulsesink->stream, NULL, NULL); + pa_stream_unref (pulsesink->stream); pulsesink->stream = NULL; } - g_mutex_unlock (pulsesink->stream_mutex); g_free (pulsesink->stream_name); pulsesink->stream_name = NULL; + + g_free (pulsesink->device_description); + pulsesink->device_description = NULL; } static void gst_pulsesink_destroy_context (GstPulseSink * pulsesink) { + gst_pulsesink_destroy_stream (pulsesink); if (pulsesink->context) { pa_context_disconnect (pulsesink->context); + + /* Make sure we don't get any further callbacks */ + pa_context_set_state_callback (pulsesink->context, NULL, NULL); + pa_context_set_subscribe_callback (pulsesink->context, NULL, NULL); + pa_context_unref (pulsesink->context); pulsesink->context = NULL; } @@ -306,9 +328,6 @@ gst_pulsesink_finalize (GObject * object) g_free (pulsesink->server); g_free (pulsesink->device); - g_free (pulsesink->stream_name); - - g_mutex_free (pulsesink->stream_mutex); pa_threaded_mainloop_free (pulsesink->mainloop); @@ -317,71 +336,181 @@ gst_pulsesink_finalize (GObject * object) pulsesink->probe = NULL; } - if (pulsesink->mixer) { - gst_pulsemixer_ctrl_free (pulsesink->mixer); - pulsesink->mixer = NULL; - } - G_OBJECT_CLASS (parent_class)->finalize (object); } -static void -gst_pulsesink_dispose (GObject * object) -{ - G_OBJECT_CLASS (parent_class)->dispose (object); -} - -#ifdef HAVE_PULSE_0_9_12 +#if HAVE_PULSE_0_9_12 static void gst_pulsesink_set_volume (GstPulseSink * pulsesink, gdouble volume) { - if (pulsesink->mixer && pulsesink->mixer->track->num_channels > 0) { - gint *volumes = g_new0 (gint, pulsesink->mixer->track->num_channels); - gint i; + pa_cvolume v; + pa_operation *o = NULL; - g_print ("setting volume for real\n"); + pa_threaded_mainloop_lock (pulsesink->mainloop); - for (i = 0; i < pulsesink->mixer->track->num_channels; i++) - volumes[i] = volume; + pulsesink->volume = volume; + pulsesink->volume_set = TRUE; - gst_pulsemixer_ctrl_set_volume (pulsesink->mixer, pulsesink->mixer->track, - volumes); + if (!pulsesink->stream) + goto unlock; - pulsesink->volume = volume; - g_free (volumes); - } else { - pulsesink->volume = volume; + gst_pulse_cvolume_from_linear (&v, pulsesink->sample_spec.channels, volume); + + if (!(o = pa_context_set_sink_input_volume (pulsesink->context, + pa_stream_get_index (pulsesink->stream), &v, NULL, NULL))) { + GST_ELEMENT_ERROR (pulsesink, RESOURCE, FAILED, + ("pa_stream_set_sink_input_volume() failed: %s", + pa_strerror (pa_context_errno (pulsesink->context))), (NULL)); + goto unlock; } + + /* We don't really care about the result of this call */ + +unlock: + + if (o) + pa_operation_unref (o); + + pa_threaded_mainloop_unlock (pulsesink->mainloop); +} + +static void +gst_pulsesink_sink_input_info_cb (pa_context * c, const pa_sink_input_info * i, + int eol, void *userdata) +{ + GstPulseSink *pulsesink = GST_PULSESINK (userdata); + + if (!i) + return; + + if (!pulsesink->stream) + return; + + g_assert (i->index == pa_stream_get_index (pulsesink->stream)); + + pulsesink->volume = pa_sw_volume_to_linear (pa_cvolume_max (&i->volume)); } static gdouble gst_pulsesink_get_volume (GstPulseSink * pulsesink) { - if (pulsesink->mixer && pulsesink->mixer->track->num_channels > 0) { - gint *volumes = g_new0 (gint, pulsesink->mixer->track->num_channels); - gdouble volume = 0.0; - gint i; + pa_operation *o = NULL; + gdouble v; - gst_pulsemixer_ctrl_get_volume (pulsesink->mixer, pulsesink->mixer->track, - volumes); + pa_threaded_mainloop_lock (pulsesink->mainloop); - for (i = 0; i < pulsesink->mixer->track->num_channels; i++) - volume += volumes[i]; - volume /= pulsesink->mixer->track->num_channels; + if (!pulsesink->stream) + goto unlock; - pulsesink->volume = volume; + if (!(o = pa_context_get_sink_input_info (pulsesink->context, + pa_stream_get_index (pulsesink->stream), + gst_pulsesink_sink_input_info_cb, pulsesink))) { - g_free (volumes); - - g_print ("real volume: %lf\n", volume); - - return volume; - } else { - return pulsesink->volume; + GST_ELEMENT_ERROR (pulsesink, RESOURCE, FAILED, + ("pa_stream_get_sink_input_info() failed: %s", + pa_strerror (pa_context_errno (pulsesink->context))), (NULL)); + goto unlock; } + + while (pa_operation_get_state (o) == PA_OPERATION_RUNNING) { + + if (gst_pulsesink_is_dead (pulsesink)) + goto unlock; + + pa_threaded_mainloop_wait (pulsesink->mainloop); + } + +unlock: + + if (o) + pa_operation_unref (o); + + v = pulsesink->volume; + + pa_threaded_mainloop_unlock (pulsesink->mainloop); + + return v; } #endif +static gboolean +gst_pulsesink_is_dead (GstPulseSink * pulsesink) +{ + + if (!pulsesink->context + || !PA_CONTEXT_IS_GOOD (pa_context_get_state (pulsesink->context)) + || !pulsesink->stream + || !PA_STREAM_IS_GOOD (pa_stream_get_state (pulsesink->stream))) { + const gchar *err_str = pulsesink->context ? + pa_strerror (pa_context_errno (pulsesink->context)) : NULL; + + GST_ELEMENT_ERROR ((pulsesink), RESOURCE, FAILED, ("Disconnected: %s", + err_str), (NULL)); + return TRUE; + } + + return FALSE; +} + +static void +gst_pulsesink_sink_info_cb (pa_context * c, const pa_sink_info * i, int eol, + void *userdata) +{ + GstPulseSink *pulsesink = GST_PULSESINK (userdata); + + if (!i) + return; + + if (!pulsesink->stream) + return; + + g_assert (i->index == pa_stream_get_device_index (pulsesink->stream)); + + g_free (pulsesink->device_description); + pulsesink->device_description = g_strdup (i->description); +} + +static gchar * +gst_pulsesink_device_description (GstPulseSink * pulsesink) +{ + pa_operation *o = NULL; + gchar *t; + + pa_threaded_mainloop_lock (pulsesink->mainloop); + + if (!pulsesink->stream) + goto unlock; + + if (!(o = pa_context_get_sink_info_by_index (pulsesink->context, + pa_stream_get_device_index (pulsesink->stream), + gst_pulsesink_sink_info_cb, pulsesink))) { + + GST_ELEMENT_ERROR (pulsesink, RESOURCE, FAILED, + ("pa_stream_get_sink_info() failed: %s", + pa_strerror (pa_context_errno (pulsesink->context))), (NULL)); + goto unlock; + } + + while (pa_operation_get_state (o) == PA_OPERATION_RUNNING) { + + if (gst_pulsesink_is_dead (pulsesink)) + goto unlock; + + pa_threaded_mainloop_wait (pulsesink->mainloop); + } + +unlock: + + if (o) + pa_operation_unref (o); + + t = g_strdup (pulsesink->device_description); + + pa_threaded_mainloop_unlock (pulsesink->mainloop); + + return t; +} + static void gst_pulsesink_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) @@ -403,7 +532,7 @@ gst_pulsesink_set_property (GObject * object, pulsesink->device = g_value_dup_string (value); break; -#ifdef HAVE_PULSE_0_9_12 +#if HAVE_PULSE_0_9_12 case PROP_VOLUME: gst_pulsesink_set_volume (pulsesink, g_value_get_double (value)); break; @@ -431,14 +560,14 @@ gst_pulsesink_get_property (GObject * object, g_value_set_string (value, pulsesink->device); break; - case PROP_DEVICE_NAME: - if (pulsesink->mixer) - g_value_set_string (value, pulsesink->mixer->description); - else - g_value_set_string (value, NULL); + case PROP_DEVICE_NAME:{ + char *t = gst_pulsesink_device_description (pulsesink); + g_value_set_string (value, t); + g_free (t); break; + } -#ifdef HAVE_PULSE_0_9_12 +#if HAVE_PULSE_0_9_12 case PROP_VOLUME: g_value_set_double (value, gst_pulsesink_get_volume (pulsesink)); break; @@ -470,6 +599,36 @@ gst_pulsesink_context_state_cb (pa_context * c, void *userdata) } } +#if HAVE_PULSE_0_9_12 +static void +gst_pulsesink_context_subscribe_cb (pa_context * c, + pa_subscription_event_type_t t, uint32_t idx, void *userdata) +{ + GstPulseSink *pulsesink = GST_PULSESINK (userdata); + + if (t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT | PA_SUBSCRIPTION_EVENT_CHANGE) && + t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT | PA_SUBSCRIPTION_EVENT_NEW)) + return; + + if (!pulsesink->stream) + return; + + if (idx != pa_stream_get_index (pulsesink->stream)) + return; + + /* Actually this event is also triggered when other properties of + * the stream change that are unrelated to the volume. However it is + * probably cheaper to signal the change here and check for the + * volume when the GObject property is read instead of querying it always. + * + * Lennart thinks this is a race because g_object_notify() is not + * thread safe and this function is run from a PA controlled + * thread. But folks on #gstreamer told me that was ok. */ + + g_object_notify (G_OBJECT (pulsesink), "volume"); +} +#endif + static void gst_pulsesink_stream_state_cb (pa_stream * s, void *userdata) { @@ -479,16 +638,8 @@ gst_pulsesink_stream_state_cb (pa_stream * s, void *userdata) case PA_STREAM_READY: case PA_STREAM_FAILED: - case PA_STREAM_TERMINATED:{ - pa_stream *cur_stream; - - g_mutex_lock (pulsesink->stream_mutex); - cur_stream = pulsesink->stream; - g_mutex_unlock (pulsesink->stream_mutex); - - if (cur_stream == s) - pa_threaded_mainloop_signal (pulsesink->mainloop, 0); - } + case PA_STREAM_TERMINATED: + pa_threaded_mainloop_signal (pulsesink->mainloop, 0); break; case PA_STREAM_UNCONNECTED: @@ -501,28 +652,16 @@ static void gst_pulsesink_stream_request_cb (pa_stream * s, size_t length, void *userdata) { GstPulseSink *pulsesink = GST_PULSESINK (userdata); - pa_stream *cur_stream; - g_mutex_lock (pulsesink->stream_mutex); - cur_stream = pulsesink->stream; - g_mutex_unlock (pulsesink->stream_mutex); - - if (cur_stream == s) - pa_threaded_mainloop_signal (pulsesink->mainloop, 0); + pa_threaded_mainloop_signal (pulsesink->mainloop, 0); } static void gst_pulsesink_stream_latency_update_cb (pa_stream * s, void *userdata) { GstPulseSink *pulsesink = GST_PULSESINK (userdata); - pa_stream *cur_stream; - g_mutex_lock (pulsesink->stream_mutex); - cur_stream = pulsesink->stream; - g_mutex_unlock (pulsesink->stream_mutex); - - if (cur_stream == s) - pa_threaded_mainloop_signal (pulsesink->mainloop, 0); + pa_threaded_mainloop_signal (pulsesink->mainloop, 0); } static gboolean @@ -530,10 +669,12 @@ gst_pulsesink_open (GstAudioSink * asink) { GstPulseSink *pulsesink = GST_PULSESINK (asink); gchar *name = gst_pulse_client_name (); - pa_context_state_t state; pa_threaded_mainloop_lock (pulsesink->mainloop); + g_assert (!pulsesink->context); + g_assert (!pulsesink->stream); + if (!(pulsesink->context = pa_context_new (pa_threaded_mainloop_get_api (pulsesink->mainloop), name))) { @@ -544,6 +685,10 @@ gst_pulsesink_open (GstAudioSink * asink) pa_context_set_state_callback (pulsesink->context, gst_pulsesink_context_state_cb, pulsesink); +#if HAVE_PULSE_0_9_12 + pa_context_set_subscribe_callback (pulsesink->context, + gst_pulsesink_context_subscribe_cb, pulsesink); +#endif if (pa_context_connect (pulsesink->context, pulsesink->server, 0, NULL) < 0) { GST_ELEMENT_ERROR (pulsesink, RESOURCE, FAILED, ("Failed to connect: %s", @@ -551,15 +696,24 @@ gst_pulsesink_open (GstAudioSink * asink) goto unlock_and_fail; } - /* Wait until the context is ready */ - pa_threaded_mainloop_wait (pulsesink->mainloop); + for (;;) { + pa_context_state_t state; - state = pa_context_get_state (pulsesink->context); - if (state != PA_CONTEXT_READY) { - GST_DEBUG_OBJECT (pulsesink, "Context state was not READY. Got: %d", state); - GST_ELEMENT_ERROR (pulsesink, RESOURCE, FAILED, ("Failed to connect: %s", - pa_strerror (pa_context_errno (pulsesink->context))), (NULL)); - goto unlock_and_fail; + state = pa_context_get_state (pulsesink->context); + + if (!PA_CONTEXT_IS_GOOD (state)) { + GST_DEBUG_OBJECT (pulsesink, "Context state was not READY. Got: %d", + state); + GST_ELEMENT_ERROR (pulsesink, RESOURCE, FAILED, ("Failed to connect: %s", + pa_strerror (pa_context_errno (pulsesink->context))), (NULL)); + goto unlock_and_fail; + } + + if (state == PA_CONTEXT_READY) + break; + + /* Wait until the context is ready */ + pa_threaded_mainloop_wait (pulsesink->mainloop); } pa_threaded_mainloop_unlock (pulsesink->mainloop); @@ -568,6 +722,8 @@ gst_pulsesink_open (GstAudioSink * asink) unlock_and_fail: + gst_pulsesink_destroy_context (pulsesink); + pa_threaded_mainloop_unlock (pulsesink->mainloop); g_free (name); return FALSE; @@ -590,37 +746,50 @@ gst_pulsesink_prepare (GstAudioSink * asink, GstRingBufferSpec * spec) { pa_buffer_attr buf_attr; pa_channel_map channel_map; - pa_stream_state_t s_state; GstPulseSink *pulsesink = GST_PULSESINK (asink); + pa_operation *o = NULL; + pa_cvolume v; if (!gst_pulse_fill_sample_spec (spec, &pulsesink->sample_spec)) { GST_ELEMENT_ERROR (pulsesink, RESOURCE, SETTINGS, ("Invalid sample specification."), (NULL)); - goto fail; + return FALSE; } pa_threaded_mainloop_lock (pulsesink->mainloop); - if (!pulsesink->context - || pa_context_get_state (pulsesink->context) != PA_CONTEXT_READY) { - GST_ELEMENT_ERROR (pulsesink, RESOURCE, FAILED, ("Bad context state: %s", - pulsesink->context ? pa_strerror (pa_context_errno (pulsesink-> - context)) : NULL), (NULL)); + if (!pulsesink->context) { + GST_ELEMENT_ERROR (pulsesink, RESOURCE, FAILED, ("Bad context"), (NULL)); goto unlock_and_fail; } - g_mutex_lock (pulsesink->stream_mutex); - if (!(pulsesink->stream = pa_stream_new (pulsesink->context, - pulsesink->stream_name ? pulsesink-> - stream_name : "Playback Stream", &pulsesink->sample_spec, - gst_pulse_gst_to_channel_map (&channel_map, spec)))) { - g_mutex_unlock (pulsesink->stream_mutex); + g_assert (!pulsesink->stream); + + if (!(o = + pa_context_subscribe (pulsesink->context, + PA_SUBSCRIPTION_MASK_SINK_INPUT, NULL, NULL))) { + const gchar *err_str = pulsesink->context ? + pa_strerror (pa_context_errno (pulsesink->context)) : NULL; + GST_ELEMENT_ERROR (pulsesink, RESOURCE, FAILED, - ("Failed to create stream: %s", - pa_strerror (pa_context_errno (pulsesink->context))), (NULL)); + ("pa_context_subscribe() failed: %s", err_str), (NULL)); + goto unlock_and_fail; + } + + pa_operation_unref (o); + + if (!(pulsesink->stream = pa_stream_new (pulsesink->context, + pulsesink->stream_name ? + pulsesink->stream_name : "Playback Stream", + &pulsesink->sample_spec, + gst_pulse_gst_to_channel_map (&channel_map, spec)))) { + const gchar *err_str = pulsesink->context ? + pa_strerror (pa_context_errno (pulsesink->context)) : NULL; + + GST_ELEMENT_ERROR (pulsesink, RESOURCE, FAILED, + ("Failed to create stream: %s", err_str), (NULL)); goto unlock_and_fail; } - g_mutex_unlock (pulsesink->stream_mutex); pa_stream_set_state_callback (pulsesink->stream, gst_pulsesink_stream_state_cb, pulsesink); @@ -632,44 +801,58 @@ gst_pulsesink_prepare (GstAudioSink * asink, GstRingBufferSpec * spec) memset (&buf_attr, 0, sizeof (buf_attr)); buf_attr.tlength = spec->segtotal * spec->segsize; buf_attr.maxlength = buf_attr.tlength * 2; - buf_attr.prebuf = buf_attr.tlength - spec->segsize; + buf_attr.prebuf = buf_attr.tlength; buf_attr.minreq = spec->segsize; + if (pulsesink->volume_set) + gst_pulse_cvolume_from_linear (&v, pulsesink->sample_spec.channels, + pulsesink->volume); + if (pa_stream_connect_playback (pulsesink->stream, pulsesink->device, &buf_attr, - PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE | - PA_STREAM_NOT_MONOTONOUS, NULL, NULL) < 0) { + PA_STREAM_INTERPOLATE_TIMING | + PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_NOT_MONOTONOUS | +#if HAVE_PULSE_0_9_11 + PA_STREAM_ADJUST_LATENCY | +#endif + PA_STREAM_START_CORKED, pulsesink->volume_set ? &v : NULL, NULL) < 0) { + GST_ELEMENT_ERROR (pulsesink, RESOURCE, FAILED, ("Failed to connect stream: %s", pa_strerror (pa_context_errno (pulsesink->context))), (NULL)); goto unlock_and_fail; } - /* Wait until the stream is ready */ - pa_threaded_mainloop_wait (pulsesink->mainloop); + for (;;) { + pa_stream_state_t state; - s_state = pa_stream_get_state (pulsesink->stream); - if (s_state != PA_STREAM_READY) { - GST_DEBUG_OBJECT (pulsesink, "Stream state was not READY. Got: %d", - s_state); - GST_ELEMENT_ERROR (pulsesink, RESOURCE, FAILED, - ("Failed to connect stream: %s", - pa_strerror (pa_context_errno (pulsesink->context))), (NULL)); - goto unlock_and_fail; + state = pa_stream_get_state (pulsesink->stream); + + if (!PA_STREAM_IS_GOOD (state)) { + GST_DEBUG_OBJECT (pulsesink, "Stream state was not READY. Got: %d", + state); + GST_ELEMENT_ERROR (pulsesink, RESOURCE, FAILED, + ("Failed to connect stream: %s", + pa_strerror (pa_context_errno (pulsesink->context))), (NULL)); + goto unlock_and_fail; + } + + if (state == PA_STREAM_READY) + break; + + /* Wait until the stream is ready */ + pa_threaded_mainloop_wait (pulsesink->mainloop); } pa_threaded_mainloop_unlock (pulsesink->mainloop); - -#ifdef HAVE_PULSE_0_9_12 - gst_pulsesink_set_volume (pulsesink, pulsesink->volume); -#endif - return TRUE; unlock_and_fail: + + gst_pulsesink_destroy_stream (pulsesink); + pa_threaded_mainloop_unlock (pulsesink->mainloop); -fail: return FALSE; } @@ -685,13 +868,6 @@ gst_pulsesink_unprepare (GstAudioSink * asink) return TRUE; } -#define CHECK_DEAD_GOTO(pulsesink, label) \ -if (!(pulsesink)->context || pa_context_get_state((pulsesink)->context) != PA_CONTEXT_READY || \ - !(pulsesink)->stream || pa_stream_get_state((pulsesink)->stream) != PA_STREAM_READY) { \ - GST_ELEMENT_ERROR((pulsesink), RESOURCE, FAILED, ("Disconnected: %s", (pulsesink)->context ? pa_strerror(pa_context_errno((pulsesink)->context)) : NULL), (NULL)); \ - goto label; \ -} - static guint gst_pulsesink_write (GstAudioSink * asink, gpointer data, guint length) { @@ -700,11 +876,14 @@ gst_pulsesink_write (GstAudioSink * asink, gpointer data, guint length) pa_threaded_mainloop_lock (pulsesink->mainloop); + pulsesink->in_write = TRUE; + while (length > 0) { size_t l; for (;;) { - CHECK_DEAD_GOTO (pulsesink, unlock_and_fail); + if (gst_pulsesink_is_dead (pulsesink)) + goto unlock_and_fail; if ((l = pa_stream_writable_size (pulsesink->stream)) == (size_t) - 1) { GST_ELEMENT_ERROR (pulsesink, RESOURCE, FAILED, @@ -716,6 +895,9 @@ gst_pulsesink_write (GstAudioSink * asink, gpointer data, guint length) if (l > 0) break; + if (pulsesink->did_reset) + goto unlock_and_fail; + pa_threaded_mainloop_wait (pulsesink->mainloop); } @@ -736,16 +918,19 @@ gst_pulsesink_write (GstAudioSink * asink, gpointer data, guint length) sum += l; } - pa_threaded_mainloop_unlock (pulsesink->mainloop); + pulsesink->did_reset = FALSE; + pulsesink->in_write = FALSE; + pa_threaded_mainloop_unlock (pulsesink->mainloop); return sum; - /* ERRORS */ unlock_and_fail: - { - pa_threaded_mainloop_unlock (pulsesink->mainloop); - return -1; - } + + pulsesink->did_reset = FALSE; + pulsesink->in_write = FALSE; + + pa_threaded_mainloop_unlock (pulsesink->mainloop); + return (guint) - 1; } static guint @@ -757,7 +942,8 @@ gst_pulsesink_delay (GstAudioSink * asink) pa_threaded_mainloop_lock (pulsesink->mainloop); for (;;) { - CHECK_DEAD_GOTO (pulsesink, unlock_and_fail); + if (gst_pulsesink_is_dead (pulsesink)) + goto unlock_and_fail; if (pa_stream_get_latency (pulsesink->stream, &t, NULL) >= 0) break; @@ -787,7 +973,7 @@ gst_pulsesink_success_cb (pa_stream * s, int success, void *userdata) { GstPulseSink *pulsesink = GST_PULSESINK (userdata); - pulsesink->operation_success = success; + pulsesink->operation_success = !!success; pa_threaded_mainloop_signal (pulsesink->mainloop, 0); } @@ -799,7 +985,8 @@ gst_pulsesink_reset (GstAudioSink * asink) pa_threaded_mainloop_lock (pulsesink->mainloop); - CHECK_DEAD_GOTO (pulsesink, unlock_and_fail); + if (gst_pulsesink_is_dead (pulsesink)) + goto unlock_and_fail; if (!(o = pa_stream_flush (pulsesink->stream, gst_pulsesink_success_cb, @@ -810,9 +997,17 @@ gst_pulsesink_reset (GstAudioSink * asink) goto unlock_and_fail; } - pulsesink->operation_success = 0; - while (pa_operation_get_state (o) != PA_OPERATION_DONE) { - CHECK_DEAD_GOTO (pulsesink, unlock_and_fail); + /* Inform anyone waiting in _write() call that it shall wakeup */ + if (pulsesink->in_write) { + pulsesink->did_reset = TRUE; + pa_threaded_mainloop_signal (pulsesink->mainloop, 0); + } + + pulsesink->operation_success = FALSE; + while (pa_operation_get_state (o) == PA_OPERATION_RUNNING) { + + if (gst_pulsesink_is_dead (pulsesink)) + goto unlock_and_fail; pa_threaded_mainloop_wait (pulsesink->mainloop); } @@ -843,25 +1038,21 @@ gst_pulsesink_change_title (GstPulseSink * pulsesink, const gchar * t) g_free (pulsesink->stream_name); pulsesink->stream_name = g_strdup (t); - if (!(pulsesink)->context - || pa_context_get_state ((pulsesink)->context) != PA_CONTEXT_READY - || !(pulsesink)->stream - || pa_stream_get_state ((pulsesink)->stream) != PA_STREAM_READY) { - goto unlock_and_fail; - } + if (gst_pulsesink_is_dead (pulsesink)) + goto unlock; if (!(o = pa_stream_set_name (pulsesink->stream, pulsesink->stream_name, NULL, - pulsesink))) { + NULL))) { GST_ELEMENT_ERROR (pulsesink, RESOURCE, FAILED, ("pa_stream_set_name() failed: %s", pa_strerror (pa_context_errno (pulsesink->context))), (NULL)); - goto unlock_and_fail; + goto unlock; } /* We're not interested if this operation failed or not */ -unlock_and_fail: +unlock: if (o) pa_operation_unref (o); @@ -869,6 +1060,74 @@ unlock_and_fail: pa_threaded_mainloop_unlock (pulsesink->mainloop); } +#if HAVE_PULSE_0_9_11 +static void +gst_pulsesink_change_props (GstPulseSink * pulsesink, GstTagList * l) +{ + + static const gchar *const map[] = { + GST_TAG_TITLE, PA_PROP_MEDIA_TITLE, + GST_TAG_ARTIST, PA_PROP_MEDIA_ARTIST, + GST_TAG_LANGUAGE_CODE, PA_PROP_MEDIA_LANGUAGE, + GST_TAG_LOCATION, PA_PROP_MEDIA_FILENAME, + /* We might add more here later on ... */ + NULL + }; + + pa_proplist *pl = NULL; + const gchar *const *t; + gboolean empty = TRUE; + pa_operation *o = NULL; + + pl = pa_proplist_new (); + + for (t = map; *t; t += 2) { + gchar *n = NULL; + + if (gst_tag_list_get_string (l, *t, &n)) { + + if (n && *n) { + pa_proplist_sets (pl, *(t + 1), n); + empty = FALSE; + } + + g_free (n); + } + } + + if (empty) + goto finish; + + pa_threaded_mainloop_lock (pulsesink->mainloop); + + if (gst_pulsesink_is_dead (pulsesink)) + goto unlock; + + if (!(o = + pa_stream_proplist_update (pulsesink->stream, PA_UPDATE_REPLACE, pl, + NULL, NULL))) { + GST_ELEMENT_ERROR (pulsesink, RESOURCE, FAILED, + ("pa_stream_proplist_update() failed: %s", + pa_strerror (pa_context_errno (pulsesink->context))), (NULL)); + goto unlock; + } + + /* We're not interested if this operation failed or not */ + +unlock: + + if (o) + pa_operation_unref (o); + + pa_threaded_mainloop_unlock (pulsesink->mainloop); + +finish: + + if (pl) + pa_proplist_free (pl); +} +#endif + static gboolean gst_pulsesink_event (GstBaseSink * sink, GstEvent * event) { @@ -907,6 +1166,10 @@ gst_pulsesink_event (GstBaseSink * sink, GstEvent * event) g_free (description); g_free (buf); +#if HAVE_PULSE_0_9_11 + gst_pulsesink_change_props (pulsesink, l); +#endif + break; } default: @@ -916,31 +1179,53 @@ gst_pulsesink_event (GstBaseSink * sink, GstEvent * event) return GST_BASE_SINK_CLASS (parent_class)->event (sink, event); } +static void +gst_pulsesink_pause (GstPulseSink * pulsesink, gboolean b) +{ + pa_operation *o = NULL; + + pa_threaded_mainloop_lock (pulsesink->mainloop); + + if (gst_pulsesink_is_dead (pulsesink)) + goto unlock; + + if (!(o = pa_stream_cork (pulsesink->stream, b, NULL, NULL))) { + + GST_ELEMENT_ERROR (pulsesink, RESOURCE, FAILED, + ("pa_stream_cork() failed: %s", + pa_strerror (pa_context_errno (pulsesink->context))), (NULL)); + goto unlock; + } + + while (pa_operation_get_state (o) == PA_OPERATION_RUNNING) { + + if (gst_pulsesink_is_dead (pulsesink)) + goto unlock; + + pa_threaded_mainloop_wait (pulsesink->mainloop); + } + +unlock: + + if (o) + pa_operation_unref (o); + + pa_threaded_mainloop_unlock (pulsesink->mainloop); +} + + static GstStateChangeReturn gst_pulsesink_change_state (GstElement * element, GstStateChange transition) { GstPulseSink *this = GST_PULSESINK (element); switch (transition) { - case GST_STATE_CHANGE_NULL_TO_READY: - if (!this->mixer) { - this->mixer = - gst_pulsemixer_ctrl_new (G_OBJECT (this), this->server, - this->device, GST_PULSEMIXER_SINK); - } - break; - - case GST_STATE_CHANGE_READY_TO_NULL: - - if (this->mixer) { -#ifdef HAVE_PULSE_0_9_12 - this->volume = gst_pulsesink_get_volume (this); -#endif - gst_pulsemixer_ctrl_free (this->mixer); - this->mixer = NULL; - } + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + gst_pulsesink_pause (this, + GST_STATE_TRANSITION_NEXT (transition) == GST_STATE_PAUSED); break; default: diff --git a/ext/pulse/pulsesink.h b/ext/pulse/pulsesink.h index e734a8c12b..eb8d43d29b 100644 --- a/ext/pulse/pulsesink.h +++ b/ext/pulse/pulsesink.h @@ -29,7 +29,6 @@ #include #include "pulseprobe.h" -#include "pulsemixerctrl.h" G_BEGIN_DECLS @@ -57,18 +56,18 @@ struct _GstPulseSink pa_context *context; pa_stream *stream; - GMutex *stream_mutex; pa_sample_spec sample_spec; - GstPulseMixerCtrl *mixer; GstPulseProbe *probe; -#if 0 gdouble volume; -#endif + gboolean volume_set; - int operation_success; + gchar *device_description; + + gboolean operation_success; + gboolean did_reset, in_write; }; struct _GstPulseSinkClass diff --git a/ext/pulse/pulsesrc.c b/ext/pulse/pulsesrc.c index 08fec59547..bd05ffa19e 100644 --- a/ext/pulse/pulsesrc.c +++ b/ext/pulse/pulsesrc.c @@ -68,8 +68,6 @@ static void gst_pulsesrc_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); static void gst_pulsesrc_finalize (GObject * object); -static void gst_pulsesrc_dispose (GObject * object); - static gboolean gst_pulsesrc_open (GstAudioSrc * asrc); static gboolean gst_pulsesrc_close (GstAudioSrc * asrc); @@ -83,6 +81,8 @@ static guint gst_pulsesrc_read (GstAudioSrc * asrc, gpointer data, guint length); static guint gst_pulsesrc_delay (GstAudioSrc * asrc); +static void gst_pulsesrc_reset (GstAudioSrc * src); + static gboolean gst_pulsesrc_negotiate (GstBaseSrc * basesrc); static GstStateChangeReturn gst_pulsesrc_change_state (GstElement * @@ -161,30 +161,30 @@ gst_pulsesrc_base_init (gpointer g_class) "width = (int) 16, " "depth = (int) 16, " "rate = (int) [ 1, MAX ], " - "channels = (int) [ 1, 16 ];" + "channels = (int) [ 1, 32 ];" + "audio/x-raw-float, " + "endianness = (int) { " ENDIANNESS " }, " + "width = (int) 32, " + "rate = (int) [ 1, MAX ], " + "channels = (int) [ 1, 32 ];" "audio/x-raw-int, " "endianness = (int) { " ENDIANNESS " }, " "signed = (boolean) TRUE, " "width = (int) 32, " "depth = (int) 32, " "rate = (int) [ 1, MAX ], " - "channels = (int) [ 1, 16 ];" - "audio/x-raw-float, " - "endianness = (int) { " ENDIANNESS " }, " - "width = (int) 32, " - "rate = (int) [ 1, MAX ], " - "channels = (int) [ 1, 16 ];" + "channels = (int) [ 1, 32 ];" "audio/x-raw-int, " "signed = (boolean) FALSE, " "width = (int) 8, " "depth = (int) 8, " "rate = (int) [ 1, MAX ], " - "channels = (int) [ 1, 16 ];" + "channels = (int) [ 1, 32 ];" "audio/x-alaw, " "rate = (int) [ 1, MAX], " - "channels = (int) [ 1, 16 ];" + "channels = (int) [ 1, 32 ];" "audio/x-mulaw, " - "rate = (int) [ 1, MAX], " "channels = (int) [ 1, 16 ]") + "rate = (int) [ 1, MAX], " "channels = (int) [ 1, 32 ]") ); GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); @@ -205,14 +205,13 @@ gst_pulsesrc_class_init (GstPulseSrcClass * klass) GstBaseSrcClass *gstbasesrc_class = GST_BASE_SRC_CLASS (klass); GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); - gstelement_class->change_state = - GST_DEBUG_FUNCPTR (gst_pulsesrc_change_state); - - gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_pulsesrc_dispose); gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_pulsesrc_finalize); gobject_class->set_property = GST_DEBUG_FUNCPTR (gst_pulsesrc_set_property); gobject_class->get_property = GST_DEBUG_FUNCPTR (gst_pulsesrc_get_property); + gstelement_class->change_state = + GST_DEBUG_FUNCPTR (gst_pulsesrc_change_state); + gstbasesrc_class->negotiate = GST_DEBUG_FUNCPTR (gst_pulsesrc_negotiate); gstaudiosrc_class->open = GST_DEBUG_FUNCPTR (gst_pulsesrc_open); @@ -221,6 +220,7 @@ gst_pulsesrc_class_init (GstPulseSrcClass * klass) gstaudiosrc_class->unprepare = GST_DEBUG_FUNCPTR (gst_pulsesrc_unprepare); gstaudiosrc_class->read = GST_DEBUG_FUNCPTR (gst_pulsesrc_read); gstaudiosrc_class->delay = GST_DEBUG_FUNCPTR (gst_pulsesrc_delay); + gstaudiosrc_class->reset = GST_DEBUG_FUNCPTR (gst_pulsesrc_reset); /* Overwrite GObject fields */ g_object_class_install_property (gobject_class, @@ -245,7 +245,7 @@ gst_pulsesrc_init (GstPulseSrc * pulsesrc, GstPulseSrcClass * klass) { int e; - pulsesrc->server = pulsesrc->device = NULL; + pulsesrc->server = pulsesrc->device = pulsesrc->device_description = NULL; pulsesrc->context = NULL; pulsesrc->stream = NULL; @@ -253,6 +253,18 @@ gst_pulsesrc_init (GstPulseSrc * pulsesrc, GstPulseSrcClass * klass) pulsesrc->read_buffer = NULL; pulsesrc->read_buffer_length = 0; +#if HAVE_PULSE_0_9_13 + pa_sample_spec_init (&pulsesrc->sample_spec); +#else + pulsesrc->sample_spec.format = PA_SAMPLE_INVALID; + pulsesrc->sample_spec.rate = 0; + pulsesrc->sample_spec.channels = 0; +#endif + + pulsesrc->operation_success = FALSE; + pulsesrc->did_reset = FALSE; + pulsesrc->in_read = FALSE; + pulsesrc->mainloop = pa_threaded_mainloop_new (); g_assert (pulsesrc->mainloop); @@ -272,6 +284,9 @@ gst_pulsesrc_destroy_stream (GstPulseSrc * pulsesrc) pa_stream_unref (pulsesrc->stream); pulsesrc->stream = NULL; } + + g_free (pulsesrc->device_description); + pulsesrc->device_description = NULL; } static void @@ -301,8 +316,10 @@ gst_pulsesrc_finalize (GObject * object) pa_threaded_mainloop_free (pulsesrc->mainloop); - if (pulsesrc->mixer) + if (pulsesrc->mixer) { gst_pulsemixer_ctrl_free (pulsesrc->mixer); + pulsesrc->mixer = NULL; + } if (pulsesrc->probe) { gst_pulseprobe_free (pulsesrc->probe); @@ -312,12 +329,26 @@ gst_pulsesrc_finalize (GObject * object) G_OBJECT_CLASS (parent_class)->finalize (object); } -static void -gst_pulsesrc_dispose (GObject * object) +static gboolean +gst_pulsesrc_is_dead (GstPulseSrc * pulsesrc) { - G_OBJECT_CLASS (parent_class)->dispose (object); + + if (!pulsesrc->context + || !PA_CONTEXT_IS_GOOD (pa_context_get_state (pulsesrc->context)) + || !pulsesrc->stream + || !PA_STREAM_IS_GOOD (pa_stream_get_state (pulsesrc->stream))) { + const gchar *err_str = pulsesrc->context ? + pa_strerror (pa_context_errno (pulsesrc->context)) : NULL; + + GST_ELEMENT_ERROR ((pulsesrc), RESOURCE, FAILED, ("Disconnected: %s", + err_str), (NULL)); + return TRUE; + } + + return FALSE; } + static void gst_pulsesrc_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) @@ -346,6 +377,65 @@ gst_pulsesrc_set_property (GObject * object, } } +static void +gst_pulsesrc_source_info_cb (pa_context * c, const pa_source_info * i, int eol, + void *userdata) +{ + GstPulseSrc *pulsesrc = GST_PULSESRC (userdata); + + if (!i) + return; + + if (!pulsesrc->stream) + return; + + g_assert (i->index == pa_stream_get_device_index (pulsesrc->stream)); + + g_free (pulsesrc->device_description); + pulsesrc->device_description = g_strdup (i->description); +} + +static gchar * +gst_pulsesrc_device_description (GstPulseSrc * pulsesrc) +{ + pa_operation *o = NULL; + gchar *t; + + pa_threaded_mainloop_lock (pulsesrc->mainloop); + + if (!pulsesrc->stream) + goto unlock; + + if (!(o = pa_context_get_source_info_by_index (pulsesrc->context, + pa_stream_get_device_index (pulsesrc->stream), + gst_pulsesrc_source_info_cb, pulsesrc))) { + + GST_ELEMENT_ERROR (pulsesrc, RESOURCE, FAILED, + ("pa_stream_get_source_info() failed: %s", + pa_strerror (pa_context_errno (pulsesrc->context))), (NULL)); + goto unlock; + } + + while (pa_operation_get_state (o) == PA_OPERATION_RUNNING) { + + if (gst_pulsesrc_is_dead (pulsesrc)) + goto unlock; + + pa_threaded_mainloop_wait (pulsesrc->mainloop); + } + +unlock: + + if (o) + pa_operation_unref (o); + + t = g_strdup (pulsesrc->device_description); + + pa_threaded_mainloop_unlock (pulsesrc->mainloop); + + return t; +} + static void gst_pulsesrc_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) @@ -362,14 +452,12 @@ gst_pulsesrc_get_property (GObject * object, g_value_set_string (value, pulsesrc->device); break; - case PROP_DEVICE_NAME: - - if (pulsesrc->mixer) - g_value_set_string (value, pulsesrc->mixer->description); - else - g_value_set_string (value, NULL); - + case PROP_DEVICE_NAME:{ + char *t = gst_pulsesrc_device_description (pulsesrc); + g_value_set_string (value, t); + g_free (t); break; + } default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -424,15 +512,25 @@ gst_pulsesrc_stream_request_cb (pa_stream * s, size_t length, void *userdata) pa_threaded_mainloop_signal (pulsesrc->mainloop, 0); } +static void +gst_pulsesrc_stream_latency_update_cb (pa_stream * s, void *userdata) +{ + GstPulseSrc *pulsesrc = GST_PULSESRC (userdata); + + pa_threaded_mainloop_signal (pulsesrc->mainloop, 0); +} + static gboolean gst_pulsesrc_open (GstAudioSrc * asrc) { GstPulseSrc *pulsesrc = GST_PULSESRC (asrc); - gchar *name = gst_pulse_client_name (); pa_threaded_mainloop_lock (pulsesrc->mainloop); + g_assert (!pulsesrc->context); + g_assert (!pulsesrc->stream); + if (!(pulsesrc->context = pa_context_new (pa_threaded_mainloop_get_api (pulsesrc->mainloop), name))) { @@ -450,13 +548,22 @@ gst_pulsesrc_open (GstAudioSrc * asrc) goto unlock_and_fail; } - /* Wait until the context is ready */ - pa_threaded_mainloop_wait (pulsesrc->mainloop); + for (;;) { + pa_context_state_t state; - if (pa_context_get_state (pulsesrc->context) != PA_CONTEXT_READY) { - GST_ELEMENT_ERROR (pulsesrc, RESOURCE, FAILED, ("Failed to connect: %s", - pa_strerror (pa_context_errno (pulsesrc->context))), (NULL)); - goto unlock_and_fail; + state = pa_context_get_state (pulsesrc->context); + + if (!PA_CONTEXT_IS_GOOD (state)) { + GST_ELEMENT_ERROR (pulsesrc, RESOURCE, FAILED, ("Failed to connect: %s", + pa_strerror (pa_context_errno (pulsesrc->context))), (NULL)); + goto unlock_and_fail; + } + + if (state == PA_CONTEXT_READY) + break; + + /* Wait until the context is ready */ + pa_threaded_mainloop_wait (pulsesrc->mainloop); } pa_threaded_mainloop_unlock (pulsesrc->mainloop); @@ -466,6 +573,8 @@ gst_pulsesrc_open (GstAudioSrc * asrc) unlock_and_fail: + gst_pulsesrc_destroy_context (pulsesrc); + pa_threaded_mainloop_unlock (pulsesrc->mainloop); g_free (name); @@ -500,23 +609,15 @@ gst_pulsesrc_unprepare (GstAudioSrc * asrc) return TRUE; } -#define CHECK_DEAD_GOTO(pulsesrc, label) \ -if (!(pulsesrc)->context || pa_context_get_state((pulsesrc)->context) != PA_CONTEXT_READY || \ - !(pulsesrc)->stream || pa_stream_get_state((pulsesrc)->stream) != PA_STREAM_READY) { \ - GST_ELEMENT_ERROR((pulsesrc), RESOURCE, FAILED, ("Disconnected: %s", (pulsesrc)->context ? pa_strerror(pa_context_errno((pulsesrc)->context)) : NULL), (NULL)); \ - goto label; \ -} - static guint gst_pulsesrc_read (GstAudioSrc * asrc, gpointer data, guint length) { GstPulseSrc *pulsesrc = GST_PULSESRC (asrc); - size_t sum = 0; pa_threaded_mainloop_lock (pulsesrc->mainloop); - CHECK_DEAD_GOTO (pulsesrc, unlock_and_fail); + pulsesrc->in_read = TRUE; while (length > 0) { size_t l; @@ -524,6 +625,9 @@ gst_pulsesrc_read (GstAudioSrc * asrc, gpointer data, guint length) if (!pulsesrc->read_buffer) { for (;;) { + if (gst_pulsesrc_is_dead (pulsesrc)) + goto unlock_and_fail; + if (pa_stream_peek (pulsesrc->stream, &pulsesrc->read_buffer, &pulsesrc->read_buffer_length) < 0) { GST_ELEMENT_ERROR (pulsesrc, RESOURCE, FAILED, @@ -535,9 +639,10 @@ gst_pulsesrc_read (GstAudioSrc * asrc, gpointer data, guint length) if (pulsesrc->read_buffer) break; - pa_threaded_mainloop_wait (pulsesrc->mainloop); + if (pulsesrc->did_reset) + goto unlock_and_fail; - CHECK_DEAD_GOTO (pulsesrc, unlock_and_fail); + pa_threaded_mainloop_wait (pulsesrc->mainloop); } } @@ -570,16 +675,19 @@ gst_pulsesrc_read (GstAudioSrc * asrc, gpointer data, guint length) } } - pa_threaded_mainloop_unlock (pulsesrc->mainloop); + pulsesrc->did_reset = FALSE; + pulsesrc->in_read = FALSE; + pa_threaded_mainloop_unlock (pulsesrc->mainloop); return sum; - /* ERRORS */ unlock_and_fail: - { - pa_threaded_mainloop_unlock (pulsesrc->mainloop); - return -1; - } + + pulsesrc->did_reset = FALSE; + pulsesrc->in_read = FALSE; + + pa_threaded_mainloop_unlock (pulsesrc->mainloop); + return (guint) - 1; } static guint @@ -593,9 +701,12 @@ gst_pulsesrc_delay (GstAudioSrc * asrc) pa_threaded_mainloop_lock (pulsesrc->mainloop); - CHECK_DEAD_GOTO (pulsesrc, unlock_and_fail); + for (;;) { + if (gst_pulsesrc_is_dead (pulsesrc)) + goto unlock_and_fail; - if (pa_stream_get_latency (pulsesrc->stream, &t, &negative) < 0) { + if (pa_stream_get_latency (pulsesrc->stream, &t, &negative) >= 0) + break; if (pa_context_errno (pulsesrc->context) != PA_ERR_NODATA) { GST_ELEMENT_ERROR (pulsesrc, RESOURCE, FAILED, @@ -604,9 +715,10 @@ gst_pulsesrc_delay (GstAudioSrc * asrc) goto unlock_and_fail; } - GST_WARNING_OBJECT (pulsesrc, "Not data while querying latency"); - t = 0; - } else if (negative) + pa_threaded_mainloop_wait (pulsesrc->mainloop); + } + + if (negative) t = 0; pa_threaded_mainloop_unlock (pulsesrc->mainloop); @@ -645,12 +757,8 @@ gst_pulsesrc_create_stream (GstPulseSrc * pulsesrc, GstCaps * caps) pa_threaded_mainloop_lock (pulsesrc->mainloop); - if (!pulsesrc->context - || pa_context_get_state (pulsesrc->context) != PA_CONTEXT_READY) { - GST_ELEMENT_ERROR (pulsesrc, RESOURCE, FAILED, ("Bad context state: %s", - pulsesrc-> - context ? pa_strerror (pa_context_errno (pulsesrc->context)) : - NULL), (NULL)); + if (!pulsesrc->context) { + GST_ELEMENT_ERROR (pulsesrc, RESOURCE, FAILED, ("Bad context"), (NULL)); goto unlock_and_fail; } @@ -688,10 +796,16 @@ gst_pulsesrc_create_stream (GstPulseSrc * pulsesrc, GstCaps * caps) pulsesrc); pa_stream_set_read_callback (pulsesrc->stream, gst_pulsesrc_stream_request_cb, pulsesrc); + pa_stream_set_latency_update_callback (pulsesrc->stream, + gst_pulsesrc_stream_latency_update_cb, pulsesrc); + + pa_threaded_mainloop_unlock (pulsesrc->mainloop); return TRUE; unlock_and_fail: + gst_pulsesrc_destroy_stream (pulsesrc); + pa_threaded_mainloop_unlock (pulsesrc->mainloop); fail: @@ -776,27 +890,42 @@ gst_pulsesrc_prepare (GstAudioSrc * asrc, GstRingBufferSpec * spec) pa_buffer_attr buf_attr; GstPulseSrc *pulsesrc = GST_PULSESRC (asrc); + pa_threaded_mainloop_lock (pulsesrc->mainloop); + memset (&buf_attr, 0, sizeof (buf_attr)); buf_attr.maxlength = spec->segtotal * spec->segsize * 2; buf_attr.fragsize = spec->segsize; if (pa_stream_connect_record (pulsesrc->stream, pulsesrc->device, &buf_attr, - PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE | - PA_STREAM_NOT_MONOTONOUS) < 0) { + PA_STREAM_INTERPOLATE_TIMING | + PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_NOT_MONOTONOUS | +#if HAVE_PULSE_0_9_11 + PA_STREAM_ADJUST_LATENCY | +#endif + PA_STREAM_START_CORKED) < 0) { GST_ELEMENT_ERROR (pulsesrc, RESOURCE, FAILED, ("Failed to connect stream: %s", pa_strerror (pa_context_errno (pulsesrc->context))), (NULL)); goto unlock_and_fail; } - /* Wait until the stream is ready */ - pa_threaded_mainloop_wait (pulsesrc->mainloop); + for (;;) { + pa_stream_state_t state; - if (pa_stream_get_state (pulsesrc->stream) != PA_STREAM_READY) { - GST_ELEMENT_ERROR (pulsesrc, RESOURCE, FAILED, - ("Failed to connect stream: %s", - pa_strerror (pa_context_errno (pulsesrc->context))), (NULL)); - goto unlock_and_fail; + state = pa_stream_get_state (pulsesrc->stream); + + if (!PA_STREAM_IS_GOOD (state)) { + GST_ELEMENT_ERROR (pulsesrc, RESOURCE, FAILED, + ("Failed to connect stream: %s", + pa_strerror (pa_context_errno (pulsesrc->context))), (NULL)); + goto unlock_and_fail; + } + + if (state == PA_STREAM_READY) + break; + + /* Wait until the stream is ready */ + pa_threaded_mainloop_wait (pulsesrc->mainloop); } pa_threaded_mainloop_unlock (pulsesrc->mainloop); @@ -804,16 +933,120 @@ gst_pulsesrc_prepare (GstAudioSrc * asrc, GstRingBufferSpec * spec) return TRUE; unlock_and_fail: + + gst_pulsesrc_destroy_stream (pulsesrc); + pa_threaded_mainloop_unlock (pulsesrc->mainloop); return FALSE; } +static void +gst_pulsesrc_success_cb (pa_stream * s, int success, void *userdata) +{ + GstPulseSrc *pulsesrc = GST_PULSESRC (userdata); + + pulsesrc->operation_success = !!success; + pa_threaded_mainloop_signal (pulsesrc->mainloop, 0); +} + +static void +gst_pulsesrc_reset (GstAudioSrc * asrc) +{ + GstPulseSrc *pulsesrc = GST_PULSESRC (asrc); + pa_operation *o = NULL; + + pa_threaded_mainloop_lock (pulsesrc->mainloop); + + if (gst_pulsesrc_is_dead (pulsesrc)) + goto unlock_and_fail; + + if (!(o = + pa_stream_flush (pulsesrc->stream, gst_pulsesrc_success_cb, + pulsesrc))) { + GST_ELEMENT_ERROR (pulsesrc, RESOURCE, FAILED, + ("pa_stream_flush() failed: %s", + pa_strerror (pa_context_errno (pulsesrc->context))), (NULL)); + goto unlock_and_fail; + } + + /* Inform anyone waiting in _write() call that it shall wakeup */ + if (pulsesrc->in_read) { + pulsesrc->did_reset = TRUE; + pa_threaded_mainloop_signal (pulsesrc->mainloop, 0); + } + + pulsesrc->operation_success = FALSE; + while (pa_operation_get_state (o) == PA_OPERATION_RUNNING) { + + if (gst_pulsesrc_is_dead (pulsesrc)) + goto unlock_and_fail; + + pa_threaded_mainloop_wait (pulsesrc->mainloop); + } + + if (!pulsesrc->operation_success) { + GST_ELEMENT_ERROR (pulsesrc, RESOURCE, FAILED, ("Flush failed: %s", + pa_strerror (pa_context_errno (pulsesrc->context))), (NULL)); + goto unlock_and_fail; + } + +unlock_and_fail: + + if (o) { + pa_operation_cancel (o); + pa_operation_unref (o); + } + + pa_threaded_mainloop_unlock (pulsesrc->mainloop); +} + +static void +gst_pulsesrc_pause (GstPulseSrc * pulsesrc, gboolean b) +{ + pa_operation *o = NULL; + + pa_threaded_mainloop_lock (pulsesrc->mainloop); + + if (gst_pulsesrc_is_dead (pulsesrc)) + goto unlock; + + if (!(o = pa_stream_cork (pulsesrc->stream, b, NULL, NULL))) { + + GST_ELEMENT_ERROR (pulsesrc, RESOURCE, FAILED, + ("pa_stream_cork() failed: %s", + pa_strerror (pa_context_errno (pulsesrc->context))), (NULL)); + goto unlock; + } + + while (pa_operation_get_state (o) == PA_OPERATION_RUNNING) { + + if (gst_pulsesrc_is_dead (pulsesrc)) + goto unlock; + + pa_threaded_mainloop_wait (pulsesrc->mainloop); + } + +unlock: + + if (o) + pa_operation_unref (o); + + pa_threaded_mainloop_unlock (pulsesrc->mainloop); +} + static GstStateChangeReturn gst_pulsesrc_change_state (GstElement * element, GstStateChange transition) { GstPulseSrc *this = GST_PULSESRC (element); switch (transition) { + + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + gst_pulsesrc_pause (this, + GST_STATE_TRANSITION_NEXT (transition) == GST_STATE_PAUSED); + break; + case GST_STATE_CHANGE_NULL_TO_READY: if (!this->mixer) diff --git a/ext/pulse/pulsesrc.h b/ext/pulse/pulsesrc.h index b07bfe2638..8c4a03b006 100644 --- a/ext/pulse/pulsesrc.h +++ b/ext/pulse/pulsesrc.h @@ -63,8 +63,13 @@ struct _GstPulseSrc const void *read_buffer; size_t read_buffer_length; + gchar *device_description; + GstPulseMixerCtrl *mixer; GstPulseProbe *probe; + + gboolean operation_success; + gboolean did_reset, in_read; }; struct _GstPulseSrcClass diff --git a/ext/pulse/pulseutil.c b/ext/pulse/pulseutil.c index 38fa7bccff..6efcf84901 100644 --- a/ext/pulse/pulseutil.c +++ b/ext/pulse/pulseutil.c @@ -200,3 +200,10 @@ gst_pulse_channel_map_to_gst (const pa_channel_map * map, return spec; } + +void +gst_pulse_cvolume_from_linear (pa_cvolume * v, unsigned channels, + gdouble volume) +{ + pa_cvolume_set (v, channels, pa_sw_volume_from_linear (volume)); +} diff --git a/ext/pulse/pulseutil.h b/ext/pulse/pulseutil.h index 8700a97930..4cafba51b2 100644 --- a/ext/pulse/pulseutil.h +++ b/ext/pulse/pulseutil.h @@ -37,4 +37,24 @@ pa_channel_map *gst_pulse_gst_to_channel_map (pa_channel_map * map, GstRingBufferSpec *gst_pulse_channel_map_to_gst (const pa_channel_map * map, GstRingBufferSpec * spec); +void gst_pulse_cvolume_from_linear(pa_cvolume *v, unsigned channels, gdouble volume); + +#if !HAVE_PULSE_0_9_11 +static inline int PA_CONTEXT_IS_GOOD(pa_context_state_t x) { + return + x == PA_CONTEXT_CONNECTING || + x == PA_CONTEXT_AUTHORIZING || + x == PA_CONTEXT_SETTING_NAME || + x == PA_CONTEXT_READY; +} + +/** Return non-zero if the passed state is one of the connected states */ +static inline int PA_STREAM_IS_GOOD(pa_stream_state_t x) { + return + x == PA_STREAM_CREATING || + x == PA_STREAM_READY; +} + +#endif + #endif