Revert "pulsesink: Make 2.0 dependency optional"
[platform/upstream/gstreamer.git] / ext / pulse / pulsesink.c
index c253832..cd257d6 100644 (file)
@@ -139,6 +139,7 @@ struct _GstPulseRingBuffer
 
   pa_context *context;
   pa_stream *stream;
+  pa_stream *probe_stream;
 
   pa_format_info *format;
   guint channels;
@@ -225,6 +226,7 @@ gst_pulseringbuffer_init (GstPulseRingBuffer * pbuf)
   pbuf->stream_name = NULL;
   pbuf->context = NULL;
   pbuf->stream = NULL;
+  pbuf->probe_stream = NULL;
 
   pbuf->format = NULL;
   pbuf->channels = 0;
@@ -241,9 +243,32 @@ gst_pulseringbuffer_init (GstPulseRingBuffer * pbuf)
   pbuf->paused = FALSE;
 }
 
+/* Call with mainloop lock held if wait == TRUE) */
+static void
+gst_pulse_destroy_stream (pa_stream * stream, gboolean wait)
+{
+  /* Make sure we don't get any further callbacks */
+  pa_stream_set_write_callback (stream, NULL, NULL);
+  pa_stream_set_underflow_callback (stream, NULL, NULL);
+  pa_stream_set_overflow_callback (stream, NULL, NULL);
+
+  pa_stream_disconnect (stream);
+
+  if (wait)
+    pa_threaded_mainloop_wait (mainloop);
+
+  pa_stream_set_state_callback (stream, NULL, NULL);
+  pa_stream_unref (stream);
+}
+
 static void
 gst_pulsering_destroy_stream (GstPulseRingBuffer * pbuf)
 {
+  if (pbuf->probe_stream) {
+    gst_pulse_destroy_stream (pbuf->probe_stream, FALSE);
+    pbuf->probe_stream = NULL;
+  }
+
   if (pbuf->stream) {
 
     if (pbuf->m_data) {
@@ -857,6 +882,13 @@ gst_pulseringbuffer_acquire (GstAudioRingBuffer * buf,
   g_assert (pbuf->context);
   g_assert (!pbuf->stream);
 
+  /* if we have a probe, disconnect it first so that if we're creating a
+   * compressed stream, it doesn't get blocked by a PCM stream */
+  if (pbuf->probe_stream) {
+    gst_pulse_destroy_stream (pbuf->probe_stream, TRUE);
+    pbuf->probe_stream = NULL;
+  }
+
   /* enable event notifications */
   GST_LOG_OBJECT (psink, "subscribing to context events");
   if (!(o = pa_context_subscribe (pbuf->context,
@@ -1985,6 +2017,265 @@ done:
 }
 
 static gboolean
+gst_pulse_format_info_int_prop_to_value (pa_format_info * format,
+    const char *key, GValue * value)
+{
+  GValue v = { 0, };
+  int i;
+  int *a, n;
+  int min, max;
+
+  if (pa_format_info_get_prop_int (format, key, &i) == 0) {
+    g_value_init (value, G_TYPE_INT);
+    g_value_set_int (value, i);
+
+  } else if (pa_format_info_get_prop_int_array (format, key, &a, &n) == 0) {
+    g_value_init (value, GST_TYPE_LIST);
+    g_value_init (&v, G_TYPE_INT);
+
+    for (i = 0; i < n; i++) {
+      g_value_set_int (&v, a[i]);
+      gst_value_list_append_value (value, &v);
+    }
+
+    pa_xfree (a);
+
+  } else if (pa_format_info_get_prop_int_range (format, key, &min, &max) == 0) {
+    g_value_init (value, GST_TYPE_INT_RANGE);
+    gst_value_set_int_range (value, min, max);
+
+  } else {
+    /* Property not available or is not an int type */
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+static GstCaps *
+gst_pulse_format_info_to_caps (pa_format_info * format)
+{
+  GstCaps *ret = NULL;
+  GValue v = { 0, };
+  pa_sample_spec ss;
+
+  switch (format->encoding) {
+    case PA_ENCODING_PCM:{
+      char *tmp = NULL;
+
+      pa_format_info_to_sample_spec (format, &ss, NULL);
+
+      if (pa_format_info_get_prop_string (format,
+              PA_PROP_FORMAT_SAMPLE_FORMAT, &tmp)) {
+        /* No specific sample format means any sample format */
+        ret = gst_caps_from_string (_PULSE_SINK_CAPS_PCM);
+        goto out;
+
+      } else if (ss.format == PA_SAMPLE_ALAW) {
+        ret = gst_caps_from_string (_PULSE_SINK_CAPS_ALAW);
+
+      } else if (ss.format == PA_SAMPLE_ULAW) {
+        ret = gst_caps_from_string (_PULSE_SINK_CAPS_MP3);
+
+      } else {
+        /* Linear PCM format */
+        const char *sformat =
+            gst_pulse_sample_format_to_caps_format (ss.format);
+
+        ret = gst_caps_from_string (_PULSE_SINK_CAPS_LINEAR);
+
+        if (sformat)
+          gst_caps_set_simple (ret, "format", G_TYPE_STRING, NULL);
+      }
+
+      pa_xfree (tmp);
+      break;
+    }
+
+    case PA_ENCODING_AC3_IEC61937:
+      ret = gst_caps_from_string (_PULSE_SINK_CAPS_AC3);
+      break;
+
+    case PA_ENCODING_EAC3_IEC61937:
+      ret = gst_caps_from_string (_PULSE_SINK_CAPS_EAC3);
+      break;
+
+    case PA_ENCODING_DTS_IEC61937:
+      ret = gst_caps_from_string (_PULSE_SINK_CAPS_DTS);
+      break;
+
+    case PA_ENCODING_MPEG_IEC61937:
+      ret = gst_caps_from_string (_PULSE_SINK_CAPS_MP3);
+      break;
+
+    default:
+      GST_WARNING ("Found a PA format that we don't support yet");
+      goto out;
+  }
+
+  if (gst_pulse_format_info_int_prop_to_value (format, PA_PROP_FORMAT_RATE, &v))
+    gst_caps_set_value (ret, "rate", &v);
+
+  g_value_unset (&v);
+
+  if (gst_pulse_format_info_int_prop_to_value (format, PA_PROP_FORMAT_CHANNELS,
+          &v))
+    gst_caps_set_value (ret, "channels", &v);
+
+out:
+  return ret;
+}
+
+/* Call with mainloop lock held */
+static pa_stream *
+gst_pulsesink_create_probe_stream (GstPulseSink * psink,
+    GstPulseRingBuffer * pbuf, pa_format_info * format)
+{
+  pa_format_info *formats[1] = { format };
+  pa_stream *stream;
+  pa_stream_flags_t flags;
+
+  GST_LOG_OBJECT (psink, "Creating probe stream");
+
+  if (!(stream = pa_stream_new_extended (pbuf->context, "pulsesink probe",
+              formats, 1, psink->proplist)))
+    goto error;
+
+  /* construct the flags */
+  flags = PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE |
+      PA_STREAM_ADJUST_LATENCY | PA_STREAM_START_CORKED;
+
+  pa_stream_set_state_callback (stream, gst_pulsering_stream_state_cb, pbuf);
+
+  if (pa_stream_connect_playback (stream, psink->device, NULL, flags, NULL,
+          NULL) < 0)
+    goto error;
+
+  if (!gst_pulsering_wait_for_stream_ready (psink, stream))
+    goto error;
+
+  return stream;
+
+error:
+  if (stream)
+    pa_stream_unref (stream);
+  return NULL;
+}
+
+static GstCaps *
+gst_pulsesink_query_getcaps (GstPulseSink * psink, GstCaps * filter)
+{
+  GstPulseRingBuffer *pbuf = NULL;
+  GstPulseDeviceInfo device_info = { NULL, NULL };
+  GstCaps *ret = NULL;
+  GList *i;
+  pa_operation *o = NULL;
+  pa_stream *stream;
+
+  GST_OBJECT_LOCK (psink);
+  pbuf = GST_PULSERING_BUFFER_CAST (GST_AUDIO_BASE_SINK (psink)->ringbuffer);
+  if (pbuf != NULL)
+    gst_object_ref (pbuf);
+  GST_OBJECT_UNLOCK (psink);
+
+  if (!pbuf) {
+    ret = gst_pad_get_pad_template_caps (GST_AUDIO_BASE_SINK_PAD (psink));
+    goto out;
+  }
+
+  GST_OBJECT_LOCK (pbuf);
+  pa_threaded_mainloop_lock (mainloop);
+
+  if (!pbuf->context) {
+    ret = gst_pad_get_pad_template_caps (GST_AUDIO_BASE_SINK_PAD (psink));
+    goto unlock;
+  }
+
+  if (pbuf->stream) {
+    /* We're in PAUSED or higher */
+    stream = pbuf->stream;
+
+  } else if (pbuf->probe_stream) {
+    /* We're not paused, but have a cached probe stream */
+    stream = pbuf->probe_stream;
+
+  } else {
+    /* We're not yet in PAUSED and still need to create a probe stream.
+     *
+     * FIXME: PA doesn't accept "any" format. We fix something reasonable since
+     * this is merely a probe. This should eventually be fixed in PA and
+     * hard-coding the format should be dropped. */
+    pa_format_info *format = pa_format_info_new ();
+    format->encoding = PA_ENCODING_PCM;
+    pa_format_info_set_sample_format (format, PA_SAMPLE_S16LE);
+    pa_format_info_set_rate (format, GST_AUDIO_DEF_RATE);
+    pa_format_info_set_channels (format, GST_AUDIO_DEF_CHANNELS);
+
+    pbuf->probe_stream = gst_pulsesink_create_probe_stream (psink, pbuf,
+        format);
+    if (!pbuf->probe_stream) {
+      GST_WARNING_OBJECT (psink, "Could not create probe stream");
+      goto unlock;
+    }
+
+    pa_format_info_free (format);
+
+    stream = pbuf->probe_stream;
+  }
+
+  ret = gst_caps_new_empty ();
+
+  if (!(o = pa_context_get_sink_info_by_name (pbuf->context,
+              pa_stream_get_device_name (stream), gst_pulsesink_sink_info_cb,
+              &device_info)))
+    goto info_failed;
+
+  while (pa_operation_get_state (o) == PA_OPERATION_RUNNING) {
+    pa_threaded_mainloop_wait (mainloop);
+    if (gst_pulsering_is_dead (psink, pbuf, FALSE))
+      goto unlock;
+  }
+
+  for (i = g_list_first (device_info.formats); i; i = g_list_next (i)) {
+    gst_caps_append (ret,
+        gst_pulse_format_info_to_caps ((pa_format_info *) i->data));
+  }
+
+  if (filter) {
+    GstCaps *tmp = gst_caps_intersect_full (filter, ret,
+        GST_CAPS_INTERSECT_FIRST);
+    gst_caps_unref (ret);
+    ret = tmp;
+  }
+
+unlock:
+  pa_threaded_mainloop_unlock (mainloop);
+  /* FIXME: this could be freed after device_name is got */
+  GST_OBJECT_UNLOCK (pbuf);
+
+out:
+  free_device_info (&device_info);
+
+  if (o)
+    pa_operation_unref (o);
+
+  if (pbuf)
+    gst_object_unref (pbuf);
+
+  GST_DEBUG_OBJECT (psink, "caps %" GST_PTR_FORMAT, ret);
+
+  return ret;
+
+info_failed:
+  {
+    GST_ELEMENT_ERROR (psink, RESOURCE, FAILED,
+        ("pa_context_get_sink_input_info() failed: %s",
+            pa_strerror (pa_context_errno (pbuf->context))), (NULL));
+    goto unlock;
+  }
+}
+
+static gboolean
 gst_pulsesink_query_acceptcaps (GstPulseSink * psink, GstCaps * caps)
 {
   GstPulseRingBuffer *pbuf = NULL;
@@ -1994,15 +2285,13 @@ gst_pulsesink_query_acceptcaps (GstPulseSink * psink, GstCaps * caps)
   gboolean ret = FALSE;
 
   GstAudioRingBufferSpec spec = { 0 };
-  pa_stream *stream = NULL;
   pa_operation *o = NULL;
   pa_channel_map channel_map;
-  pa_stream_flags_t flags;
-  pa_format_info *format = NULL, *formats[1];
+  pa_format_info *format = NULL;
   guint channels;
 
-  pad_caps = gst_pad_query_caps (GST_BASE_SINK_PAD (psink), caps);
-  ret = pad_caps != NULL;
+  pad_caps = gst_pad_get_pad_template_caps (GST_BASE_SINK_PAD (psink));
+  ret = gst_caps_is_subset (caps, pad_caps);
   gst_caps_unref (pad_caps);
 
   GST_DEBUG_OBJECT (psink, "caps %" GST_PTR_FORMAT, caps);
@@ -2026,6 +2315,7 @@ gst_pulsesink_query_acceptcaps (GstPulseSink * psink, GstCaps * caps)
   if (pbuf == NULL)
     goto done;
 
+  GST_OBJECT_LOCK (pbuf);
   pa_threaded_mainloop_lock (mainloop);
 
   if (pbuf->context == NULL)
@@ -2056,18 +2346,20 @@ gst_pulsesink_query_acceptcaps (GstPulseSink * psink, GstCaps * caps)
       gst_pulse_gst_to_channel_map (&channel_map, &spec))
     pa_format_info_set_channel_map (format, &channel_map);
 
-  if (pbuf->stream) {
+  if (pbuf->stream || pbuf->probe_stream) {
     /* We're already in PAUSED or above, so just reuse this stream to query
      * sink formats and use those. */
     GList *i;
+    const char *device_name = pa_stream_get_device_name (pbuf->stream ?
+        pbuf->stream : pbuf->probe_stream);
 
-    if (!(o = pa_context_get_sink_info_by_name (pbuf->context, psink->device,
+    if (!(o = pa_context_get_sink_info_by_name (pbuf->context, device_name,
                 gst_pulsesink_sink_info_cb, &device_info)))
       goto info_failed;
 
     while (pa_operation_get_state (o) == PA_OPERATION_RUNNING) {
       pa_threaded_mainloop_wait (mainloop);
-      if (gst_pulsering_is_dead (psink, pbuf, TRUE))
+      if (gst_pulsering_is_dead (psink, pbuf, FALSE))
         goto out;
     }
 
@@ -2079,24 +2371,11 @@ gst_pulsesink_query_acceptcaps (GstPulseSink * psink, GstCaps * caps)
     }
   } else {
     /* We're in READY, let's connect a stream to see if the format is
-     * accpeted by whatever sink we're routed to */
-    formats[0] = format;
-
-    if (!(stream = pa_stream_new_extended (pbuf->context, "pulsesink probe",
-                formats, 1, psink->proplist)))
-      goto out;
-
-    /* construct the flags */
-    flags = PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE |
-        PA_STREAM_ADJUST_LATENCY | PA_STREAM_START_CORKED;
-
-    pa_stream_set_state_callback (stream, gst_pulsering_stream_state_cb, pbuf);
-
-    if (pa_stream_connect_playback (stream, psink->device, NULL, flags, NULL,
-            NULL) < 0)
-      goto out;
-
-    ret = gst_pulsering_wait_for_stream_ready (psink, stream);
+     * accepted by whatever sink we're routed to */
+    pbuf->probe_stream = gst_pulsesink_create_probe_stream (psink, pbuf,
+        format);
+    if (pbuf->probe_stream)
+      ret = TRUE;
   }
 
 out:
@@ -2106,14 +2385,8 @@ out:
   if (o)
     pa_operation_unref (o);
 
-  if (stream) {
-    free_device_info (&device_info);
-    pa_stream_set_state_callback (stream, NULL, NULL);
-    pa_stream_disconnect (stream);
-    pa_stream_unref (stream);
-  }
-
   pa_threaded_mainloop_unlock (mainloop);
+  GST_OBJECT_UNLOCK (pbuf);
 
   gst_caps_replace (&spec.caps, NULL);
   gst_object_unref (pbuf);
@@ -2852,6 +3125,21 @@ gst_pulsesink_query (GstBaseSink * sink, GstQuery * query)
   gboolean ret;
 
   switch (GST_QUERY_TYPE (query)) {
+    case GST_QUERY_CAPS:
+    {
+      GstCaps *caps, *filter;
+
+      gst_query_parse_caps (query, &filter);
+      caps = gst_pulsesink_query_getcaps (pulsesink, filter);
+
+      if (caps) {
+        gst_query_set_caps_result (query, caps);
+        gst_caps_unref (caps);
+        return TRUE;
+      } else {
+        return FALSE;
+      }
+    }
     case GST_QUERY_ACCEPT_CAPS:
     {
       GstCaps *caps;