mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-01-11 09:55:36 +00:00
Rewrite the pulse plugin, conditionally enabling new behaviour with
newer pulseaudio. Fixes: #567794 * Hook pulsesink's volume property up with the stream volume -- not the sink volume in PA. * Read the device description directly from the sink instead of going via the mixer. * Properly implement _reset() methods for both sink and source to avoid deadlocks when shutting down a pipeline. * Replace all simple pa_threaded_mainloop_wait() by proper loops to guarantee that we wait for the right event in case multiple events are fired. While this is not strictly necessary in many cases it certainly is more correct and makes me sleep better at night. * Replace CHECK_DEAD_GOTO macros with proper functions * Extend the number of supported channels to 32 since that is the actual limit in PA. * Get rid of _dispose() methods since we don't need them. * Increase the volume property upper limit of the sink to 1000. * Reset function pointers after we disconnect a stream/context. Better fix for bug 556986. * Reset the state of the element properly if open/prepare fails * Cork the PA stream when the pipeline is paused. This allows the PA * daemon to close audio device on pause and thus save a bit of power. * Set PA stream properties based on GST tags such as GST_TAG_TITLE, GST_TAG_ARTIST, and so on. Signed-off-by: Lennart Poettering <lennart@poettering.net>
This commit is contained in:
parent
9cf73bdd8f
commit
0037635bf2
9 changed files with 873 additions and 282 deletions
12
configure.ac
12
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 ***
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue