*
* You should have received a copy of the GNU Lesser General Public
* License along with gst-pulse; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
* USA.
*/
/**
* SECTION:element-pulsesink
- * @see_also: pulsesrc, pulsemixer
+ * @see_also: pulsesrc
*
* This element outputs audio to a
* <ulink href="http://www.pulseaudio.org">PulseAudio sound server</ulink>.
* <refsect2>
* <title>Example pipelines</title>
* |[
- * gst-launch -v filesrc location=sine.ogg ! oggdemux ! vorbisdec ! audioconvert ! audioresample ! pulsesink
+ * gst-launch-1.0 -v filesrc location=sine.ogg ! oggdemux ! vorbisdec ! audioconvert ! audioresample ! pulsesink
* ]| Play an Ogg/Vorbis file.
* |[
- * gst-launch -v audiotestsrc ! audioconvert ! volume volume=0.4 ! pulsesink
+ * gst-launch-1.0 -v audiotestsrc ! audioconvert ! volume volume=0.4 ! pulsesink
* ]| Play a 440Hz sine wave.
* |[
- * gst-launch -v audiotestsrc ! pulsesink stream-properties="props,media.title=test"
+ * gst-launch-1.0 -v audiotestsrc ! pulsesink stream-properties="props,media.title=test"
* ]| Play a sine wave and set a stream property. The property can be checked
* with "pactl list".
* </refsect2>
#include <gst/base/gstbasesink.h>
#include <gst/gsttaglist.h>
-#include <gst/audio/streamvolume.h>
+#include <gst/audio/audio.h>
#include <gst/gst-i18n-plugin.h>
-#include <gst/audio/gstaudioiec61937.h>
#include <gst/pbutils/pbutils.h> /* only used for GST_PLUGINS_BASE_VERSION_* */
#include <gst/glib-compat-private.h>
-
+#if defined(__TIZEN__) && defined(PCM_DUMP_ENABLE)
+#include <vconf.h>
+#endif /* __TIZEN__ && PCM_DUMP_ENABLE */
#include "pulsesink.h"
#include "pulseutil.h"
#define DEFAULT_SERVER NULL
#define DEFAULT_DEVICE NULL
+#define DEFAULT_CURRENT_DEVICE NULL
#define DEFAULT_DEVICE_NAME NULL
#define DEFAULT_VOLUME 1.0
#define DEFAULT_MUTE FALSE
#define MAX_VOLUME 10.0
+#ifdef __TIZEN__
+#define DEFAULT_AUDIO_LATENCY "mid"
+#define DEFAULT_AUTO_RENDER_DELAY FALSE
+#endif /* __TIZEN__ */
enum
{
PROP_0,
PROP_SERVER,
PROP_DEVICE,
+ PROP_CURRENT_DEVICE,
PROP_DEVICE_NAME,
PROP_VOLUME,
PROP_MUTE,
PROP_CLIENT_NAME,
PROP_STREAM_PROPERTIES,
+#ifdef __TIZEN__
+ PROP_AUDIO_LATENCY,
+ PROP_AUTO_RENDER_DELAY,
+#endif /* __TIZEN__ */
PROP_LAST
};
+#if defined(__TIZEN__) && defined(PCM_DUMP_ENABLE)
+#define GST_PULSESINK_DUMP_VCONF_KEY "memory/private/sound/pcm_dump"
+#define GST_PULSESINK_DUMP_INPUT_PATH_PREFIX "/tmp/dump_pulsesink_in_"
+#define GST_PULSESINK_DUMP_OUTPUT_PATH_PREFIX "/tmp/dump_pulsesink_out_"
+#define GST_PULSESINK_DUMP_INPUT_FLAG 0x00000400
+#define GST_PULSESINK_DUMP_OUTPUT_FLAG 0x00000800
+#endif /* __TIZEN__ && PCM_DUMP_ENABLE */
+
#define GST_TYPE_PULSERING_BUFFER \
(gst_pulseringbuffer_get_type())
#define GST_PULSERING_BUFFER(obj) \
typedef struct _GstPulseContext GstPulseContext;
+/* A note on threading.
+ *
+ * We use a pa_threaded_mainloop to interact with the PulseAudio server. This
+ * starts up a separate thread that runs a mainloop to carry back events,
+ * messages and timing updates from the PulseAudio server.
+ *
+ * In most cases, the PulseAudio API we use communicates with the server and
+ * processes replies asynchronously. Operations on PA objects that result in
+ * such communication are protected with a pa_threaded_mainloop_lock() and
+ * pa_threaded_mainloop_unlock(). These guarantee mutual exclusion with the
+ * mainloop thread -- when an iteration of the mainloop thread begins, it first
+ * tries to acquire this lock, and cannot do so if our code also holds that
+ * lock.
+ *
+ * When we need to complete an operation synchronously, we use
+ * pa_threaded_mainloop_wait() and pa_threaded_mainloop_signal(). These work
+ * much as pthread conditionals do. pa_threaded_mainloop_wait() is called with
+ * the mainloop lock held. It releases the lock (thereby allowing the mainloop
+ * to execute), and waits till one of our callbacks to be executed by the
+ * mainloop thread calls pa_threaded_mainloop_signal(). At the end of the
+ * mainloop iteration, the pa_threaded_mainloop_wait() will reacquire the
+ * mainloop lock and return control to the caller.
+ */
+
/* Store the PA contexts in a hash table to allow easy sharing among
* multiple instances of the sink. Keys are $context_name@$server_name
* (strings) and values should be GstPulseContext pointers.
pa_context *context;
pa_stream *stream;
+ pa_stream *probe_stream;
pa_format_info *format;
guint channels;
static guint gst_pulseringbuffer_commit (GstAudioRingBuffer * buf,
guint64 * sample, guchar * data, gint in_samples, gint out_samples,
gint * accum);
+#ifdef __TIZEN__
+static gboolean gst_pulsering_set_corked (GstPulseRingBuffer * pbuf, gboolean corked,
+ gboolean wait);
+#endif
G_DEFINE_TYPE (GstPulseRingBuffer, gst_pulseringbuffer,
GST_TYPE_AUDIO_RING_BUFFER);
pbuf->stream_name = NULL;
pbuf->context = NULL;
pbuf->stream = NULL;
+ pbuf->probe_stream = NULL;
pbuf->format = NULL;
pbuf->channels = 0;
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) {
GstPulseRingBuffer *pbuf = (GstPulseRingBuffer *) walk->data;
psink = GST_PULSESINK_CAST (GST_OBJECT_PARENT (pbuf));
- GST_LOG_OBJECT (psink, "type %d, idx %u", t, idx);
+ GST_LOG_OBJECT (psink, "type %04x, idx %u", t, idx);
if (!pbuf->stream)
continue;
pa_threaded_mainloop_wait (mainloop);
}
+ if (pa_context_get_server_protocol_version (pbuf->context) < 22) {
+ /* We need PulseAudio >= 1.0 on the server side for the extended API */
+ goto bad_server_version;
+ }
+
GST_LOG_OBJECT (psink, "opened the device");
pa_threaded_mainloop_unlock (mainloop);
pa_strerror (pa_context_errno (pctx->context))), (NULL));
goto unlock_and_fail;
}
+bad_server_version:
+ {
+ GST_ELEMENT_ERROR (psink, RESOURCE, FAILED, ("PulseAudio server version "
+ "is too old."), (NULL));
+ goto unlock_and_fail;
+ }
}
/* close the device */
gst_pulsering_destroy_context (pbuf);
pa_threaded_mainloop_unlock (mainloop);
+#if defined(__TIZEN__) && defined(PCM_DUMP_ENABLE)
+ if (psink->dump_fd_input) {
+ fclose(psink->dump_fd_input);
+ psink->dump_fd_input = NULL;
+ }
+#endif /* __TIZEN__ && PCM_DUMP_ENABLE */
+
GST_LOG_OBJECT (psink, "closed device");
return TRUE;
{
GstPulseSink *psink;
GstPulseRingBuffer *pbuf;
+ GstAudioRingBuffer *ringbuf;
const pa_timing_info *info;
pa_usec_t sink_usec;
pbuf = GST_PULSERING_BUFFER_CAST (userdata);
psink = GST_PULSESINK_CAST (GST_OBJECT_PARENT (pbuf));
+ ringbuf = GST_AUDIO_RING_BUFFER (pbuf);
if (!info) {
GST_LOG_OBJECT (psink, "latency update (information unknown)");
return;
}
+
+ if (!info->read_index_corrupt) {
+ /* Update segdone based on the read index. segdone is of segment
+ * granularity, while the read index is at byte granularity. We take the
+ * ceiling while converting the latter to the former since it is more
+ * conservative to report that we've read more than we have than to report
+ * less. One concern here is that latency updates happen every 100ms, which
+ * means segdone is not updated very often, but increasing the update
+ * frequency would mean more communication overhead. */
+ g_atomic_int_set (&ringbuf->segdone,
+ (int) gst_util_uint64_scale_ceil (info->read_index, 1,
+ ringbuf->spec.segsize));
+ }
+
sink_usec = info->configured_sink_usec;
GST_LOG_OBJECT (psink,
GST_TIMEVAL_TO_TIME (info->timestamp), info->write_index_corrupt,
info->write_index, info->read_index_corrupt, info->read_index,
info->sink_usec, sink_usec);
+#ifdef __TIZEN__
+ if (!psink->auto_render_delay)
+ return;
+
+ if (sink_usec < info->sink_usec)
+ gst_base_sink_set_render_delay (GST_BASE_SINK(psink),
+ (info->sink_usec - sink_usec) * G_GINT64_CONSTANT (1000));
+ else
+ gst_base_sink_set_render_delay (GST_BASE_SINK(psink), 0);
+
+ GST_DEBUG_OBJECT (psink,
+ "Current render delay is %llu", gst_base_sink_get_render_delay (GST_BASE_SINK(psink)));
+#endif
}
static void
GST_ELEMENT_ERROR (psink, STREAM, FORMAT, ("Sink format changed"),
("Sink format changed"));
}
+#ifdef __TIZEN__
+ } else if (!strcmp (name, PA_STREAM_EVENT_POP_TIMEOUT)) {
+ GST_WARNING_OBJECT (psink, "got event [%s], cork stream now!!!!", name);
+ gst_pulsering_set_corked (pbuf, TRUE, FALSE);
+#endif
} else {
GST_DEBUG_OBJECT (psink, "got unknown event %s", name);
}
const pa_buffer_attr *actual;
pa_channel_map channel_map;
pa_operation *o = NULL;
+#ifndef __TIZEN__
pa_cvolume v;
+#endif
pa_cvolume *pv = NULL;
pa_stream_flags_t flags;
const gchar *name;
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,
else
name = "Playback Stream";
+#if defined(__TIZEN__) && defined(PCM_DUMP_ENABLE)
+ if (psink->need_dump_input == TRUE && psink->dump_fd_input == NULL) {
+ char *suffix , *dump_path;
+ GDateTime *time = g_date_time_new_now_local();
+
+ suffix = g_date_time_format(time, "%m%d_%H%M%S");
+ dump_path = g_strdup_printf("%s%dch_%dhz_%s.pcm", GST_PULSESINK_DUMP_INPUT_PATH_PREFIX, pbuf->channels, spec->info.rate, suffix);
+ GST_WARNING_OBJECT(psink, "pulse-sink dumping enabled: dump path [%s]", dump_path);
+ psink->dump_fd_input = fopen(dump_path, "w+");
+
+ g_free(suffix);
+ g_free(dump_path);
+ g_date_time_unref(time);
+ }
+#endif /* __TIZEN__ && PCM_DUMP_ENABLE */
+
/* create a stream */
formats[0] = pbuf->format;
if (!(pbuf->stream = pa_stream_new_extended (pbuf->context, name, formats, 1,
GST_INFO_OBJECT (psink, "prebuf: %d", wanted.prebuf);
GST_INFO_OBJECT (psink, "minreq: %d", wanted.minreq);
+#ifndef __TIZEN__
/* configure volume when we changed it, else we leave the default */
if (psink->volume_set) {
GST_LOG_OBJECT (psink, "have volume of %f", psink->volume);
} else {
pv = NULL;
}
+#endif
/* construct the flags */
flags = PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE |
PA_STREAM_ADJUST_LATENCY | PA_STREAM_START_CORKED;
- if (psink->mute_set && psink->mute)
- flags |= PA_STREAM_START_MUTED;
+#ifndef __TIZEN__
+ if (psink->mute_set) {
+ if (psink->mute)
+ flags |= PA_STREAM_START_MUTED;
+ else
+ flags |= PA_STREAM_START_UNMUTED;
+ }
+#endif
/* we always start corked (see flags above) */
pbuf->corked = TRUE;
GST_INFO_OBJECT (psink, "negotiated to: %s", print_buf);
#endif
+#ifdef __TIZEN__
+ {
+ uint32_t idx;
+ if ((idx = pa_stream_get_index (pbuf->stream)) == PA_INVALID_INDEX)
+ goto no_index;
+ if (psink->volume_set)
+ gst_pulse_set_volume_ratio (idx, "out", psink->volume);
+ if (psink->mute_set)
+ if (psink->mute)
+ gst_pulse_set_volume_ratio (idx, "out", 0);
+ }
+#endif
/* After we passed the volume off of to PA we never want to set it
again, since it is PA's job to save/restore volumes. */
psink->volume_set = psink->mute_set = FALSE;
pa_strerror (pa_context_errno (pbuf->context))), (NULL));
goto unlock_and_fail;
}
+#ifdef __TIZEN__
+no_index:
+ {
+ GST_ELEMENT_ERROR (psink, RESOURCE, FAILED,
+ ("Failed to get stream index: %s",
+ pa_strerror (pa_context_errno (pbuf->context))), (NULL));
+ goto unlock_and_fail;
+ }
+#endif
}
/* free the stream that we acquired before */
pa_threaded_mainloop_unlock (mainloop);
}
-/* called from pulse with the mainloop lock */
+#if 0
+/* called from pulse thread with the mainloop lock */
static void
mainloop_enter_defer_cb (pa_mainloop_api * api, void *userdata)
{
GstMessage *message;
GValue val = { 0 };
- g_value_init (&val, G_TYPE_POINTER);
- g_value_set_pointer (&val, g_thread_self ());
-
GST_DEBUG_OBJECT (pulsesink, "posting ENTER stream status");
message = gst_message_new_stream_status (GST_OBJECT (pulsesink),
GST_STREAM_STATUS_TYPE_ENTER, GST_ELEMENT (pulsesink));
+ g_value_init (&val, GST_TYPE_G_THREAD);
+ g_value_set_boxed (&val, g_thread_self ());
gst_message_set_stream_status_object (message, &val);
+ g_value_unset (&val);
gst_element_post_message (GST_ELEMENT (pulsesink), message);
pulsesink->defer_pending--;
pa_threaded_mainloop_signal (mainloop, 0);
}
+#endif
/* start/resume playback ASAP, we don't uncork here but in the commit method */
static gboolean
pa_threaded_mainloop_lock (mainloop);
- GST_DEBUG_OBJECT (psink, "scheduling stream status");
- psink->defer_pending++;
- pa_mainloop_api_once (pa_threaded_mainloop_get_api (mainloop),
- mainloop_enter_defer_cb, psink);
-
GST_DEBUG_OBJECT (psink, "starting");
pbuf->paused = FALSE;
g_atomic_int_get (&GST_AUDIO_BASE_SINK (psink)->eos_rendering))
gst_pulsering_set_corked (pbuf, FALSE, FALSE);
+#if 0
+ GST_DEBUG_OBJECT (psink, "scheduling stream status");
+ psink->defer_pending++;
+ pa_mainloop_api_once (pa_threaded_mainloop_get_api (mainloop),
+ mainloop_enter_defer_cb, psink);
+
+ /* Wait for the stream status message to be posted. This needs to be done
+ * synchronously because the callback will take the mainloop lock
+ * (implicitly) and then take the GST_OBJECT_LOCK. Everywhere else, we take
+ * the locks in the reverse order, so not doing this synchronously could
+ * cause a deadlock. */
+ GST_DEBUG_OBJECT (psink, "waiting for stream status (ENTER) to be posted");
+ pa_threaded_mainloop_wait (mainloop);
+#endif
+
pa_threaded_mainloop_unlock (mainloop);
return TRUE;
return res;
}
-/* called from pulse with the mainloop lock */
+#if 0
+/* called from pulse thread with the mainloop lock */
static void
mainloop_leave_defer_cb (pa_mainloop_api * api, void *userdata)
{
GstMessage *message;
GValue val = { 0 };
- g_value_init (&val, G_TYPE_POINTER);
- g_value_set_pointer (&val, g_thread_self ());
-
GST_DEBUG_OBJECT (pulsesink, "posting LEAVE stream status");
message = gst_message_new_stream_status (GST_OBJECT (pulsesink),
GST_STREAM_STATUS_TYPE_LEAVE, GST_ELEMENT (pulsesink));
+ g_value_init (&val, GST_TYPE_G_THREAD);
+ g_value_set_boxed (&val, g_thread_self ());
gst_message_set_stream_status_object (message, &val);
+ g_value_unset (&val);
+
gst_element_post_message (GST_ELEMENT (pulsesink), message);
g_return_if_fail (pulsesink->defer_pending);
pulsesink->defer_pending--;
pa_threaded_mainloop_signal (mainloop, 0);
}
+#endif
/* stop playback, we flush everything. */
static gboolean
pa_operation_cancel (o);
pa_operation_unref (o);
}
-
+#if 0
GST_DEBUG_OBJECT (psink, "scheduling stream status");
psink->defer_pending++;
pa_mainloop_api_once (pa_threaded_mainloop_get_api (mainloop),
mainloop_leave_defer_cb, psink);
+ /* Wait for the stream status message to be posted. This needs to be done
+ * synchronously because the callback will take the mainloop lock
+ * (implicitly) and then take the GST_OBJECT_LOCK. Everywhere else, we take
+ * the locks in the reverse order, so not doing this synchronously could
+ * cause a deadlock. */
+ GST_DEBUG_OBJECT (psink, "waiting for stream status (LEAVE) to be posted");
+ pa_threaded_mainloop_wait (mainloop);
+#endif
+
pa_threaded_mainloop_unlock (mainloop);
return res;
if (g_atomic_int_compare_and_exchange (&psink->notify, 1, 0)) {
g_object_notify (G_OBJECT (psink), "volume");
g_object_notify (G_OBJECT (psink), "mute");
+ g_object_notify (G_OBJECT (psink), "current-device");
}
/* make sure the ringbuffer is started */
if (pbuf->paused)
goto was_paused;
+#ifdef __TIZEN__
+ /* ensure running clock for whatever out there */
+ if (pbuf->corked) {
+ if (!gst_pulsering_set_corked (pbuf, FALSE, FALSE))
+ goto uncork_failed;
+ }
+#endif
/* offset is in bytes */
offset = *sample * bpf;
static GstStateChangeReturn gst_pulsesink_change_state (GstElement * element,
GstStateChange transition);
-static GstStaticPadTemplate pad_template = GST_STATIC_PAD_TEMPLATE ("sink",
- GST_PAD_SINK,
- GST_PAD_ALWAYS,
- GST_STATIC_CAPS (PULSE_SINK_TEMPLATE_CAPS));
-
#define gst_pulsesink_parent_class parent_class
G_DEFINE_TYPE_WITH_CODE (GstPulseSink, gst_pulsesink, GST_TYPE_AUDIO_BASE_SINK,
gst_pulsesink_init_contexts ();
case GST_AUDIO_RING_BUFFER_FORMAT_TYPE_EAC3:
case GST_AUDIO_RING_BUFFER_FORMAT_TYPE_DTS:
case GST_AUDIO_RING_BUFFER_FORMAT_TYPE_MPEG:
+ case GST_AUDIO_RING_BUFFER_FORMAT_TYPE_MPEG2_AAC:
+ case GST_AUDIO_RING_BUFFER_FORMAT_TYPE_MPEG4_AAC:
{
/* FIXME: alloc memory from PA if possible */
gint framesize = gst_audio_iec61937_frame_size (&sink->ringbuffer->spec);
gst_buffer_map (out, &outmap, GST_MAP_WRITE);
res = gst_audio_iec61937_payload (inmap.data, inmap.size,
- outmap.data, outmap.size, &sink->ringbuffer->spec);
+ outmap.data, outmap.size, &sink->ringbuffer->spec, G_BIG_ENDIAN);
gst_buffer_unmap (buf, &inmap);
gst_buffer_unmap (out, &outmap);
}
}
+#if defined(__TIZEN__) && defined(PCM_DUMP_ENABLE)
+static GstPadProbeReturn
+gst_pulsesink_pad_dump_probe (GstPad * pad, GstPadProbeInfo * info, gpointer data)
+{
+ GstPulseSink *psink = GST_PULSESINK_CAST (data);
+ size_t written = 0;
+ GstBuffer *buffer = GST_PAD_PROBE_INFO_BUFFER (info);
+ GstMapInfo in_map;
+ if (psink->dump_fd_input) {
+ gst_buffer_map(buffer, &in_map, GST_MAP_READ);
+ written = fwrite(in_map.data, 1, in_map.size, psink->dump_fd_input);
+ if (written != in_map.size)
+ GST_WARNING("failed to write!!! ferror=%d", ferror(psink->dump_fd_input));
+ gst_buffer_unmap(buffer, &in_map);
+ }
+ return GST_PAD_PROBE_OK;
+}
+#endif /* __TIZEN__ && PCM_DUMP_ENABLE */
+
static void
gst_pulsesink_class_init (GstPulseSinkClass * klass)
{
GstBaseSinkClass *bc;
GstAudioBaseSinkClass *gstaudiosink_class = GST_AUDIO_BASE_SINK_CLASS (klass);
GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
+ GstCaps *caps;
gchar *clientname;
gobject_class->finalize = gst_pulsesink_finalize;
"The PulseAudio sink device to connect to", DEFAULT_DEVICE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_CURRENT_DEVICE,
+ g_param_spec_string ("current-device", "Current Device",
+ "The current PulseAudio sink device", DEFAULT_CURRENT_DEVICE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
g_object_class_install_property (gobject_class,
PROP_DEVICE_NAME,
g_param_spec_string ("device-name", "Device name",
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
- * GstPulseSink:client-name
+ * GstPulseSink:client-name:
*
* The PulseAudio client name to use.
*/
g_free (clientname);
/**
- * GstPulseSink:stream-properties
+ * GstPulseSink:stream-properties:
*
* List of pulseaudio stream properties. A list of defined properties can be
* found in the <ulink url="http://0pointer.de/lennart/projects/pulseaudio/doxygen/proplist_8h.html">pulseaudio api docs</ulink>.
* g_object_set (pulse, "stream-properties", props, NULL);
* gst_structure_free
* ]|
- *
- * Since: 0.10.26
*/
g_object_class_install_property (gobject_class,
PROP_STREAM_PROPERTIES,
"list of pulseaudio stream properties",
GST_TYPE_STRUCTURE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
- gst_element_class_set_details_simple (gstelement_class,
+#ifdef __TIZEN__
+ g_object_class_install_property (gobject_class,
+ PROP_AUDIO_LATENCY,
+ g_param_spec_string ("latency", "Audio Backend Latency",
+ "Audio Backend Latency (\"low\": Low Latency, \"mid\": Mid Latency, \"high\": High Latency)",
+ DEFAULT_AUDIO_LATENCY,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class,
+ PROP_AUTO_RENDER_DELAY,
+ g_param_spec_boolean ("auto-render-delay", "Auto Render Delay",
+ "Apply render delay automatically", DEFAULT_AUTO_RENDER_DELAY,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+#endif /* __TIZEN__ */
+
+ gst_element_class_set_static_metadata (gstelement_class,
"PulseAudio Audio Sink",
"Sink/Audio", "Plays audio to a PulseAudio server", "Lennart Poettering");
+
+ caps =
+ gst_pulse_fix_pcm_caps (gst_caps_from_string (PULSE_SINK_TEMPLATE_CAPS));
gst_element_class_add_pad_template (gstelement_class,
- gst_static_pad_template_get (&pad_template));
+ gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, caps));
+ gst_caps_unref (caps);
+}
+
+static void
+free_device_info (GstPulseDeviceInfo * device_info)
+{
+ GList *l;
+
+ g_free (device_info->description);
+
+ for (l = g_list_first (device_info->formats); l; l = g_list_next (l))
+ pa_format_info_free ((pa_format_info *) l->data);
+
+ g_list_free (device_info->formats);
}
-/* returns the current time of the sink ringbuffer */
+/* Returns the current time of the sink ringbuffer. The timing_info is updated
+ * on every data write/flush and every 100ms (PA_STREAM_AUTO_TIMING_UPDATE).
+ */
static GstClockTime
gst_pulsesink_get_time (GstClock * clock, GstAudioBaseSink * sink)
{
gst_pulsesink_sink_info_cb (pa_context * c, const pa_sink_info * i, int eol,
void *userdata)
{
- GstPulseRingBuffer *pbuf;
- GstPulseSink *psink;
- GList *l;
+ GstPulseDeviceInfo *device_info = (GstPulseDeviceInfo *) userdata;
guint8 j;
- pbuf = GST_PULSERING_BUFFER_CAST (userdata);
- psink = GST_PULSESINK_CAST (GST_OBJECT_PARENT (pbuf));
-
if (!i)
goto done;
- g_free (psink->device_description);
- psink->device_description = g_strdup (i->description);
-
- g_mutex_lock (&psink->sink_formats_lock);
-
- for (l = g_list_first (psink->sink_formats); l; l = g_list_next (l))
- pa_format_info_free ((pa_format_info *) l->data);
-
- g_list_free (psink->sink_formats);
- psink->sink_formats = NULL;
+ device_info->description = g_strdup (i->description);
+ device_info->formats = NULL;
for (j = 0; j < i->n_formats; j++)
- psink->sink_formats = g_list_prepend (psink->sink_formats,
+ device_info->formats = g_list_prepend (device_info->formats,
pa_format_info_copy (i->formats[j]));
- g_mutex_unlock (&psink->sink_formats_lock);
-
done:
pa_threaded_mainloop_signal (mainloop, 0);
}
+/* 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;
+ }
+
+ ret = gst_caps_new_empty ();
+
+ 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);
+
+ pa_format_info_free (format);
+
+ if (!pbuf->probe_stream) {
+ GST_WARNING_OBJECT (psink, "Could not create probe stream");
+ goto unlock;
+ }
+
+ stream = pbuf->probe_stream;
+ }
+
+ 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)) {
+ GstCaps *caps = gst_pulse_format_info_to_caps ((pa_format_info *) i->data);
+ if (caps)
+ gst_caps_append (ret, caps);
+ }
+
+unlock:
+ pa_threaded_mainloop_unlock (mainloop);
+ /* FIXME: this could be freed after device_name is got */
+ GST_OBJECT_UNLOCK (pbuf);
+
+ if (filter) {
+ GstCaps *tmp = gst_caps_intersect_full (filter, ret,
+ GST_CAPS_INTERSECT_FIRST);
+ gst_caps_unref (ret);
+ ret = tmp;
+ }
+
+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 = GST_PULSERING_BUFFER_CAST (GST_AUDIO_BASE_SINK
- (psink)->ringbuffer);
- GstPad *pad = GST_BASE_SINK_PAD (psink);
+ GstPulseRingBuffer *pbuf = NULL;
+ GstPulseDeviceInfo device_info = { NULL, NULL };
GstCaps *pad_caps;
GstStructure *st;
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 (pad, 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);
- /* Either template caps didn't match, or we're still in NULL state */
- if (!ret || !pbuf->context)
+ GST_DEBUG_OBJECT (psink, "caps %" GST_PTR_FORMAT, caps);
+
+ /* Template caps didn't match */
+ if (!ret)
goto done;
/* If we've not got fixed caps, creating a stream might fail, so let's just
if (!gst_caps_is_fixed (caps))
goto done;
- ret = FALSE;
+ 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);
+ /* We're still in NULL state */
+ if (pbuf == NULL)
+ goto done;
+
+ GST_OBJECT_LOCK (pbuf);
pa_threaded_mainloop_lock (mainloop);
+ if (pbuf->context == NULL)
+ goto out;
+
+ ret = FALSE;
+
spec.latency_time = GST_AUDIO_BASE_SINK (psink)->latency_time;
if (!gst_audio_ring_buffer_parse_caps (&spec, caps))
goto out;
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,
- gst_pulsesink_sink_info_cb, pbuf)))
+ 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;
}
- g_mutex_lock (&psink->sink_formats_lock);
- for (i = g_list_first (psink->sink_formats); i; i = g_list_next (i)) {
+ for (i = g_list_first (device_info.formats); i; i = g_list_next (i)) {
if (pa_format_info_is_compatible ((pa_format_info *) i->data, format)) {
ret = TRUE;
break;
}
}
- g_mutex_unlock (&psink->sink_formats_lock);
} 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:
if (format)
pa_format_info_free (format);
+ free_device_info (&device_info);
+
if (o)
pa_operation_unref (o);
- if (stream) {
- 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);
done:
+
return ret;
info_failed:
static void
gst_pulsesink_init (GstPulseSink * pulsesink)
{
+#if defined(__TIZEN__) && defined(PCM_DUMP_ENABLE)
+ GstPad *sinkpad = NULL;
+ int vconf_dump = 0;
+#endif /* __TIZEN__ && PCM_DUMP_ENABLE */
+
pulsesink->server = NULL;
pulsesink->device = NULL;
- pulsesink->device_description = NULL;
+ pulsesink->device_info.description = NULL;
pulsesink->client_name = gst_pulse_client_name ();
- g_mutex_init (&pulsesink->sink_formats_lock);
- pulsesink->sink_formats = NULL;
+ pulsesink->device_info.formats = NULL;
pulsesink->volume = DEFAULT_VOLUME;
pulsesink->volume_set = FALSE;
pulsesink->properties = NULL;
pulsesink->proplist = NULL;
+#ifdef __TIZEN__
+ pulsesink->latency = g_strdup (DEFAULT_AUDIO_LATENCY);
+ pulsesink->auto_render_delay = DEFAULT_AUTO_RENDER_DELAY;
+ pulsesink->proplist = pa_proplist_new();
+ pa_proplist_sets(pulsesink->proplist, PA_PROP_MEDIA_TIZEN_AUDIO_LATENCY, pulsesink->latency);
+#ifdef PCM_DUMP_ENABLE
+ if (vconf_get_int(GST_PULSESINK_DUMP_VCONF_KEY, &vconf_dump)) {
+ GST_WARNING("vconf_get_int %s failed", GST_PULSESINK_DUMP_VCONF_KEY);
+ }
+ pulsesink->need_dump_input = vconf_dump & GST_PULSESINK_DUMP_INPUT_FLAG ? TRUE : FALSE;
+ pulsesink->dump_fd_input = NULL;
+ if (pulsesink->need_dump_input) {
+ sinkpad = gst_element_get_static_pad((GstElement *)pulsesink, "sink");
+ if (sinkpad) {
+ gst_pad_add_probe (sinkpad, GST_PAD_PROBE_TYPE_BUFFER, gst_pulsesink_pad_dump_probe, pulsesink, NULL);
+ gst_object_unref (GST_OBJECT(sinkpad));
+ }
+ }
+#endif
+#endif /* __TIZEN__ */
/* override with a custom clock */
if (GST_AUDIO_BASE_SINK (pulsesink)->provided_clock)
GST_AUDIO_BASE_SINK (pulsesink)->provided_clock =
gst_audio_clock_new ("GstPulseSinkClock",
(GstAudioClockGetTimeFunc) gst_pulsesink_get_time, pulsesink, NULL);
-
- /* TRUE for sinks, FALSE for sources */
- pulsesink->probe = gst_pulseprobe_new (G_OBJECT (pulsesink),
- G_OBJECT_GET_CLASS (pulsesink), PROP_DEVICE, pulsesink->device,
- TRUE, FALSE);
}
static void
gst_pulsesink_finalize (GObject * object)
{
GstPulseSink *pulsesink = GST_PULSESINK_CAST (object);
- GList *i;
g_free (pulsesink->server);
g_free (pulsesink->device);
- g_free (pulsesink->device_description);
g_free (pulsesink->client_name);
+ g_free (pulsesink->current_sink_name);
- for (i = g_list_first (pulsesink->sink_formats); i; i = g_list_next (i))
- pa_format_info_free ((pa_format_info *) i->data);
-
- g_list_free (pulsesink->sink_formats);
- g_mutex_clear (&pulsesink->sink_formats_lock);
+ free_device_info (&pulsesink->device_info);
if (pulsesink->properties)
gst_structure_free (pulsesink->properties);
if (pulsesink->proplist)
pa_proplist_free (pulsesink->proplist);
- if (pulsesink->probe) {
- gst_pulseprobe_free (pulsesink->probe);
- pulsesink->probe = NULL;
- }
+#ifdef __TIZEN__
+ g_free (pulsesink->latency);
+#endif /* __TIZEN__ */
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
gst_pulsesink_set_volume (GstPulseSink * psink, gdouble volume)
{
+#ifndef __TIZEN__
pa_cvolume v;
pa_operation *o = NULL;
+#endif
GstPulseRingBuffer *pbuf;
uint32_t idx;
+#ifndef __TIZEN__
if (!mainloop)
goto no_mainloop;
pa_threaded_mainloop_lock (mainloop);
+#endif
GST_DEBUG_OBJECT (psink, "setting volume to %f", volume);
if ((idx = pa_stream_get_index (pbuf->stream)) == PA_INVALID_INDEX)
goto no_index;
+#ifndef __TIZEN__
if (pbuf->is_pcm)
gst_pulse_cvolume_from_linear (&v, pbuf->channels, volume);
else
&v, NULL, NULL)))
goto volume_failed;
+#else
+ if (!psink->mute)
+ gst_pulse_set_volume_ratio (idx, "out", volume);
+ psink->volume = volume;
+#endif
+
/* We don't really care about the result of this call */
unlock:
+#ifndef __TIZEN__
if (o)
pa_operation_unref (o);
pa_threaded_mainloop_unlock (mainloop);
+#endif
return;
/* ERRORS */
+#ifndef __TIZEN__
no_mainloop:
{
psink->volume = volume;
GST_DEBUG_OBJECT (psink, "we have no mainloop");
return;
}
+#endif
no_buffer:
{
psink->volume = volume;
GST_DEBUG_OBJECT (psink, "we don't have a stream index");
goto unlock;
}
+#ifndef __TIZEN__
volume_failed:
{
GST_ELEMENT_ERROR (psink, RESOURCE, FAILED,
pa_strerror (pa_context_errno (pbuf->context))), (NULL));
goto unlock;
}
+#endif
}
static void
gst_pulsesink_set_mute (GstPulseSink * psink, gboolean mute)
{
+#ifndef __TIZEN__
pa_operation *o = NULL;
+#endif
GstPulseRingBuffer *pbuf;
uint32_t idx;
+#ifndef __TIZEN__
if (!mainloop)
goto no_mainloop;
pa_threaded_mainloop_lock (mainloop);
+#endif
GST_DEBUG_OBJECT (psink, "setting mute state to %d", mute);
if ((idx = pa_stream_get_index (pbuf->stream)) == PA_INVALID_INDEX)
goto no_index;
+#ifndef __TIZEN__
if (!(o = pa_context_set_sink_input_mute (pbuf->context, idx,
mute, NULL, NULL)))
goto mute_failed;
+#else
+ gst_pulse_set_volume_ratio (idx, "out", mute ? 0 : psink->volume);
+ psink->mute = mute;
+#endif
/* We don't really care about the result of this call */
unlock:
+#ifndef __TIZEN__
if (o)
pa_operation_unref (o);
pa_threaded_mainloop_unlock (mainloop);
+#endif
return;
/* ERRORS */
+#ifndef __TIZEN__
no_mainloop:
{
psink->mute = mute;
GST_DEBUG_OBJECT (psink, "we have no mainloop");
return;
}
+#endif
no_buffer:
{
psink->mute = mute;
GST_DEBUG_OBJECT (psink, "we don't have a stream index");
goto unlock;
}
+#ifndef __TIZEN__
mute_failed:
{
GST_ELEMENT_ERROR (psink, RESOURCE, FAILED,
pa_strerror (pa_context_errno (pbuf->context))), (NULL));
goto unlock;
}
+#endif
}
static void
if (i->index == pa_stream_get_index (pbuf->stream)) {
psink->volume = pa_sw_volume_to_linear (pa_cvolume_max (&i->volume));
psink->mute = i->mute;
+ psink->current_sink_idx = i->sink;
+
+ if (psink->volume > MAX_VOLUME) {
+ GST_WARNING_OBJECT (psink, "Clipped volume from %f to %f", psink->volume,
+ MAX_VOLUME);
+ psink->volume = MAX_VOLUME;
+ }
}
done:
pa_threaded_mainloop_signal (mainloop, 0);
}
-static gdouble
-gst_pulsesink_get_volume (GstPulseSink * psink)
+static void
+gst_pulsesink_get_sink_input_info (GstPulseSink * psink, gdouble * volume,
+ gboolean * mute)
{
GstPulseRingBuffer *pbuf;
pa_operation *o = NULL;
- gdouble v = DEFAULT_VOLUME;
uint32_t idx;
if (!mainloop)
}
unlock:
- v = psink->volume;
+ if (volume)
+ *volume = psink->volume;
+ if (mute)
+ *mute = psink->mute;
if (o)
pa_operation_unref (o);
pa_threaded_mainloop_unlock (mainloop);
- if (v > MAX_VOLUME) {
- GST_WARNING_OBJECT (psink, "Clipped volume from %f to %f", v, MAX_VOLUME);
- v = MAX_VOLUME;
- }
-
- return v;
+ return;
/* ERRORS */
no_mainloop:
{
- v = psink->volume;
+ if (volume)
+ *volume = psink->volume;
+ if (mute)
+ *mute = psink->mute;
+
GST_DEBUG_OBJECT (psink, "we have no mainloop");
- return v;
+ return;
}
no_buffer:
{
}
}
-static gboolean
-gst_pulsesink_get_mute (GstPulseSink * psink)
+static void
+gst_pulsesink_current_sink_info_cb (pa_context * c, const pa_sink_info * i,
+ int eol, void *userdata)
+{
+ GstPulseSink *psink;
+
+ psink = GST_PULSESINK_CAST (userdata);
+
+ if (!i)
+ goto done;
+
+ /* If the index doesn't match our current stream,
+ * it implies we just recreated the stream (caps change)
+ */
+ if (i->index == psink->current_sink_idx) {
+ g_free (psink->current_sink_name);
+ psink->current_sink_name = g_strdup (i->name);
+ }
+
+done:
+ pa_threaded_mainloop_signal (mainloop, 0);
+}
+
+static gchar *
+gst_pulsesink_get_current_device (GstPulseSink * pulsesink)
{
- GstPulseRingBuffer *pbuf;
pa_operation *o = NULL;
- uint32_t idx;
- gboolean mute = FALSE;
+ GstPulseRingBuffer *pbuf;
+ gchar *current_sink;
if (!mainloop)
goto no_mainloop;
- pa_threaded_mainloop_lock (mainloop);
- mute = psink->mute;
-
- pbuf = GST_PULSERING_BUFFER_CAST (GST_AUDIO_BASE_SINK (psink)->ringbuffer);
+ pbuf =
+ GST_PULSERING_BUFFER_CAST (GST_AUDIO_BASE_SINK (pulsesink)->ringbuffer);
if (pbuf == NULL || pbuf->stream == NULL)
goto no_buffer;
- if ((idx = pa_stream_get_index (pbuf->stream)) == PA_INVALID_INDEX)
- goto no_index;
+ gst_pulsesink_get_sink_input_info (pulsesink, NULL, NULL);
- if (!(o = pa_context_get_sink_input_info (pbuf->context, idx,
- gst_pulsesink_sink_input_info_cb, pbuf)))
+ pa_threaded_mainloop_lock (mainloop);
+
+ if (!(o = pa_context_get_sink_info_by_index (pbuf->context,
+ pulsesink->current_sink_idx, gst_pulsesink_current_sink_info_cb,
+ pulsesink)))
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 (pulsesink, pbuf, TRUE))
goto unlock;
}
unlock:
+
+ current_sink = g_strdup (pulsesink->current_sink_name);
+
if (o)
pa_operation_unref (o);
pa_threaded_mainloop_unlock (mainloop);
- return mute;
+ return current_sink;
/* ERRORS */
no_mainloop:
{
- mute = psink->mute;
- GST_DEBUG_OBJECT (psink, "we have no mainloop");
- return mute;
+ GST_DEBUG_OBJECT (pulsesink, "we have no mainloop");
+ return NULL;
}
no_buffer:
{
- GST_DEBUG_OBJECT (psink, "we have no ringbuffer");
- goto unlock;
- }
-no_index:
- {
- GST_DEBUG_OBJECT (psink, "we don't have a stream index");
- goto unlock;
+ GST_DEBUG_OBJECT (pulsesink, "we have no ringbuffer");
+ return NULL;
}
info_failed:
{
- GST_ELEMENT_ERROR (psink, RESOURCE, FAILED,
+ GST_ELEMENT_ERROR (pulsesink, RESOURCE, FAILED,
("pa_context_get_sink_input_info() failed: %s",
pa_strerror (pa_context_errno (pbuf->context))), (NULL));
goto unlock;
if (pbuf == NULL)
goto no_buffer;
+ free_device_info (&psink->device_info);
if (!(o = pa_context_get_sink_info_by_name (pbuf->context,
- psink->device, gst_pulsesink_sink_info_cb, pbuf)))
+ psink->device, gst_pulsesink_sink_info_cb, &psink->device_info)))
goto info_failed;
while (pa_operation_get_state (o) == PA_OPERATION_RUNNING) {
if (o)
pa_operation_unref (o);
- t = g_strdup (psink->device_description);
+ t = g_strdup (psink->device_info.description);
pa_threaded_mainloop_unlock (mainloop);
return t;
}
static void
+gst_pulsesink_set_stream_device (GstPulseSink * psink, const gchar * device)
+{
+ pa_operation *o = NULL;
+ GstPulseRingBuffer *pbuf;
+ uint32_t idx;
+
+ if (!mainloop)
+ goto no_mainloop;
+
+ pa_threaded_mainloop_lock (mainloop);
+
+ pbuf = GST_PULSERING_BUFFER_CAST (GST_AUDIO_BASE_SINK (psink)->ringbuffer);
+ if (pbuf == NULL || pbuf->stream == NULL)
+ goto no_buffer;
+
+ if ((idx = pa_stream_get_index (pbuf->stream)) == PA_INVALID_INDEX)
+ goto no_index;
+
+
+ GST_DEBUG_OBJECT (psink, "setting stream device to %s", device);
+
+ if (!(o = pa_context_move_sink_input_by_name (pbuf->context, idx, device,
+ NULL, NULL)))
+ goto move_failed;
+
+unlock:
+
+ if (o)
+ pa_operation_unref (o);
+
+ pa_threaded_mainloop_unlock (mainloop);
+
+ return;
+
+ /* ERRORS */
+no_mainloop:
+ {
+ GST_DEBUG_OBJECT (psink, "we have no mainloop");
+ return;
+ }
+no_buffer:
+ {
+ GST_DEBUG_OBJECT (psink, "we have no ringbuffer");
+ goto unlock;
+ }
+no_index:
+ {
+ GST_DEBUG_OBJECT (psink, "we don't have a stream index");
+ return;
+ }
+move_failed:
+ {
+ GST_ELEMENT_ERROR (psink, RESOURCE, FAILED,
+ ("pa_context_move_sink_input_by_name(%s) failed: %s", device,
+ pa_strerror (pa_context_errno (pbuf->context))), (NULL));
+ goto unlock;
+ }
+}
+
+
+static void
gst_pulsesink_set_property (GObject * object,
guint prop_id, const GValue * value, GParamSpec * pspec)
{
case PROP_SERVER:
g_free (pulsesink->server);
pulsesink->server = g_value_dup_string (value);
- if (pulsesink->probe)
- gst_pulseprobe_set_server (pulsesink->probe, pulsesink->server);
break;
case PROP_DEVICE:
g_free (pulsesink->device);
pulsesink->device = g_value_dup_string (value);
+ gst_pulsesink_set_stream_device (pulsesink, pulsesink->device);
break;
case PROP_VOLUME:
gst_pulsesink_set_volume (pulsesink, g_value_get_double (value));
pa_proplist_free (pulsesink->proplist);
pulsesink->proplist = gst_pulse_make_proplist (pulsesink->properties);
break;
+#ifdef __TIZEN__
+ case PROP_AUDIO_LATENCY:
+ g_free (pulsesink->latency);
+ pulsesink->latency = g_value_dup_string (value);
+ /* setting NULL restores the default latency */
+ if (pulsesink->latency == NULL) {
+ pulsesink->latency = g_strdup (DEFAULT_AUDIO_LATENCY);
+ }
+ if (!pulsesink->proplist) {
+ pulsesink->proplist = pa_proplist_new();
+ }
+ pa_proplist_sets(pulsesink->proplist, PA_PROP_MEDIA_TIZEN_AUDIO_LATENCY, pulsesink->latency);
+ GST_DEBUG_OBJECT(pulsesink, "latency(%s)", pulsesink->latency);
+ break;
+ case PROP_AUTO_RENDER_DELAY:
+ pulsesink->auto_render_delay = g_value_get_boolean (value);
+ GST_DEBUG_OBJECT (pulsesink, "setting auto-render-delay to %d", g_value_get_boolean (value));
+ break;
+#endif /* __TIZEN__ */
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
case PROP_DEVICE:
g_value_set_string (value, pulsesink->device);
break;
+ case PROP_CURRENT_DEVICE:
+ {
+ gchar *current_device = gst_pulsesink_get_current_device (pulsesink);
+ if (current_device)
+ g_value_take_string (value, current_device);
+ else
+ g_value_set_string (value, "");
+ break;
+ }
case PROP_DEVICE_NAME:
g_value_take_string (value, gst_pulsesink_device_description (pulsesink));
break;
case PROP_VOLUME:
- g_value_set_double (value, gst_pulsesink_get_volume (pulsesink));
+ {
+#ifndef __TIZEN__
+ gdouble volume;
+
+ gst_pulsesink_get_sink_input_info (pulsesink, &volume, NULL);
+ g_value_set_double (value, volume);
+#else
+ g_value_set_double (value, pulsesink->volume);
+#endif
break;
+ }
case PROP_MUTE:
- g_value_set_boolean (value, gst_pulsesink_get_mute (pulsesink));
+ {
+#ifndef __TIZEN__
+ gboolean mute;
+
+ gst_pulsesink_get_sink_input_info (pulsesink, NULL, &mute);
+ g_value_set_boolean (value, mute);
+#else
+ g_value_set_boolean (value, pulsesink->mute);
+#endif
break;
+ }
case PROP_CLIENT_NAME:
g_value_set_string (value, pulsesink->client_name);
break;
case PROP_STREAM_PROPERTIES:
gst_value_set_structure (value, pulsesink->properties);
break;
+#ifdef __TIZEN__
+ case PROP_AUDIO_LATENCY:
+ g_value_set_string (value, pulsesink->latency);
+ break;
+ case PROP_AUTO_RENDER_DELAY:
+ g_value_set_boolean (value, pulsesink->auto_render_delay);
+ break;
+#endif /* __TIZEN__ */
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
if (pbuf == NULL || pbuf->stream == NULL)
goto no_buffer;
+ /* We're not interested if this operation failed or not */
if (!(o = pa_stream_proplist_update (pbuf->stream, PA_UPDATE_REPLACE,
- pl, NULL, NULL)))
- goto update_failed;
+ pl, NULL, NULL))) {
+ GST_DEBUG_OBJECT (psink, "pa_stream_proplist_update() failed");
+ }
- /* We're not interested if this operation failed or not */
unlock:
if (o)
GST_DEBUG_OBJECT (psink, "we have no ringbuffer");
goto unlock;
}
-update_failed:
- {
- GST_ELEMENT_ERROR (psink, RESOURCE, FAILED,
- ("pa_stream_proplist_update() failed: %s",
- pa_strerror (pa_context_errno (pbuf->context))), (NULL));
- goto unlock;
- }
}
static void
break;
}
+ case GST_EVENT_GAP:{
+ GstClockTime timestamp, duration;
+
+ gst_event_parse_gap (event, ×tamp, &duration);
+ if (duration == GST_CLOCK_TIME_NONE)
+ gst_pulsesink_flush_ringbuffer (pulsesink);
+ break;
+ }
case GST_EVENT_EOS:
gst_pulsesink_flush_ringbuffer (pulsesink);
break;
gst_pulsesink_query (GstBaseSink * sink, GstQuery * query)
{
GstPulseSink *pulsesink = GST_PULSESINK_CAST (sink);
- gboolean ret;
+ gboolean ret = FALSE;
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);
+ ret = TRUE;
+ }
+ break;
+ }
case GST_QUERY_ACCEPT_CAPS:
{
GstCaps *caps;