diff options
Diffstat (limited to 'ext/pulse/pulsesink.c')
-rw-r--r-- | ext/pulse/pulsesink.c | 637 |
1 files changed, 461 insertions, 176 deletions
diff --git a/ext/pulse/pulsesink.c b/ext/pulse/pulsesink.c index b41dba50..75ac84de 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); } +#if HAVE_PULSE_0_9_12 static void -gst_pulsesink_dispose (GObject * object) +gst_pulsesink_set_volume (GstPulseSink * pulsesink, gdouble volume) { - G_OBJECT_CLASS (parent_class)->dispose (object); + pa_cvolume v; + pa_operation *o = NULL; + + pa_threaded_mainloop_lock (pulsesink->mainloop); + + pulsesink->volume = volume; + pulsesink->volume_set = TRUE; + + if (!pulsesink->stream) + goto unlock; + + 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); } -#ifdef HAVE_PULSE_0_9_12 static void -gst_pulsesink_set_volume (GstPulseSink * pulsesink, gdouble volume) +gst_pulsesink_sink_input_info_cb (pa_context * c, const pa_sink_input_info * i, + int eol, void *userdata) { - if (pulsesink->mixer && pulsesink->mixer->track->num_channels > 0) { - gint *volumes = g_new0 (gint, pulsesink->mixer->track->num_channels); - gint i; + GstPulseSink *pulsesink = GST_PULSESINK (userdata); - g_print ("setting volume for real\n"); + if (!i) + return; - for (i = 0; i < pulsesink->mixer->track->num_channels; i++) - volumes[i] = volume; + if (!pulsesink->stream) + return; - gst_pulsemixer_ctrl_set_volume (pulsesink->mixer, pulsesink->mixer->track, - volumes); + g_assert (i->index == pa_stream_get_index (pulsesink->stream)); - pulsesink->volume = volume; - g_free (volumes); - } else { - pulsesink->volume = volume; - } + 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))) { + + GST_ELEMENT_ERROR (pulsesink, RESOURCE, FAILED, + ("pa_stream_get_sink_input_info() failed: %s", + pa_strerror (pa_context_errno (pulsesink->context))), (NULL)); + goto unlock; + } - g_free (volumes); + while (pa_operation_get_state (o) == PA_OPERATION_RUNNING) { - g_print ("real volume: %lf\n", volume); + if (gst_pulsesink_is_dead (pulsesink)) + goto unlock; - return volume; - } else { - return pulsesink->volume; + 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); + 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, + ("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, + 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); + 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)); + ("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); - pa_threaded_mainloop_unlock (pulsesink->mainloop); + 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; + } -#ifdef HAVE_PULSE_0_9_12 - gst_pulsesink_set_volume (pulsesink, pulsesink->volume); -#endif + if (state == PA_STREAM_READY) + break; + + /* Wait until the stream is ready */ + pa_threaded_mainloop_wait (pulsesink->mainloop); + } + pa_threaded_mainloop_unlock (pulsesink->mainloop); 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,31 +1038,95 @@ 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); + + 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: |