summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--configure.ac12
-rw-r--r--ext/pulse/pulseprobe.c72
-rw-r--r--ext/pulse/pulseprobe.h2
-rw-r--r--ext/pulse/pulsesink.c637
-rw-r--r--ext/pulse/pulsesink.h11
-rw-r--r--ext/pulse/pulsesrc.c377
-rw-r--r--ext/pulse/pulsesrc.h5
-rw-r--r--ext/pulse/pulseutil.c7
-rw-r--r--ext/pulse/pulseutil.h20
9 files changed, 867 insertions, 276 deletions
diff --git a/configure.ac b/configure.ac
index 2727448c..b2c35e86 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 59a250be..588a9c39 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 3baf6db6..bd20591a 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 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:
diff --git a/ext/pulse/pulsesink.h b/ext/pulse/pulsesink.h
index e734a8c1..eb8d43d2 100644
--- a/ext/pulse/pulsesink.h
+++ b/ext/pulse/pulsesink.h
@@ -29,7 +29,6 @@
#include <pulse/thread-mainloop.h>
#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 08fec595..bd05ffa1 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 ];"
- "audio/x-raw-int, "
+ "channels = (int) [ 1, 32 ];"
+ "audio/x-raw-float, "
"endianness = (int) { " ENDIANNESS " }, "
- "signed = (boolean) TRUE, "
"width = (int) 32, "
- "depth = (int) 32, "
"rate = (int) [ 1, MAX ], "
- "channels = (int) [ 1, 16 ];"
- "audio/x-raw-float, "
+ "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);
@@ -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)
@@ -347,6 +378,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 b07bfe26..8c4a03b0 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 38fa7bcc..6efcf849 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 8700a979..4cafba51 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