/* * This file is part of the Nice GLib ICE library. * * (C) 2006, 2007 Collabora Ltd. * Contact: Dafydd Harries * (C) 2006, 2007 Nokia Corporation. All rights reserved. * Contact: Kai Vehmanen * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the Nice GLib ICE library. * * The Initial Developers of the Original Code are Collabora Ltd and Nokia * Corporation. All Rights Reserved. * * Contributors: * Dafydd Harries, Collabora Ltd. * * Alternatively, the contents of this file may be used under the terms of the * the GNU Lesser General Public License Version 2.1 (the "LGPL"), in which * case the provisions of LGPL are applicable instead of those above. If you * wish to allow use of your version of this file only under the terms of the * LGPL and not to allow others to use your version of this file under the * MPL, indicate your decision by deleting the provisions above and replace * them with the notice and other provisions required by the LGPL. If you do * not delete the provisions above, a recipient may use your version of this * file under either the MPL or the LGPL. */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "gstnicesink.h" GST_DEBUG_CATEGORY_STATIC (nicesink_debug); #define GST_CAT_DEFAULT nicesink_debug static GstFlowReturn gst_nice_sink_render ( GstBaseSink *basesink, GstBuffer *buffer); static GstFlowReturn gst_nice_sink_render_list ( GstBaseSink *basesink, GstBufferList *buffer_list); static gboolean gst_nice_sink_unlock (GstBaseSink *basesink); static gboolean gst_nice_sink_unlock_stop (GstBaseSink *basesink); static void _reliable_transport_writable ( NiceAgent *agent, guint stream_id, guint component_id, GstNiceSink *sink); static void gst_nice_sink_set_property ( GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void gst_nice_sink_get_property ( GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void gst_nice_sink_dispose (GObject *object); static void gst_nice_sink_finalize (GObject *object); static GstStateChangeReturn gst_nice_sink_change_state ( GstElement * element, GstStateChange transition); static GstStaticPadTemplate gst_nice_sink_sink_template = GST_STATIC_PAD_TEMPLATE ( "sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS_ANY); G_DEFINE_TYPE (GstNiceSink, gst_nice_sink, GST_TYPE_BASE_SINK); enum { PROP_AGENT = 1, PROP_STREAM, PROP_COMPONENT }; static void gst_nice_sink_class_init (GstNiceSinkClass *klass) { GstBaseSinkClass *gstbasesink_class; GstElementClass *gstelement_class; GObjectClass *gobject_class; GST_DEBUG_CATEGORY_INIT (nicesink_debug, "nicesink", 0, "libnice sink"); gstbasesink_class = (GstBaseSinkClass *) klass; gstbasesink_class->render = GST_DEBUG_FUNCPTR (gst_nice_sink_render); gstbasesink_class->render_list = GST_DEBUG_FUNCPTR (gst_nice_sink_render_list); gstbasesink_class->unlock = GST_DEBUG_FUNCPTR (gst_nice_sink_unlock); gstbasesink_class->unlock_stop = GST_DEBUG_FUNCPTR (gst_nice_sink_unlock_stop); gobject_class = (GObjectClass *) klass; gobject_class->set_property = gst_nice_sink_set_property; gobject_class->get_property = gst_nice_sink_get_property; gobject_class->dispose = gst_nice_sink_dispose; gobject_class->finalize = gst_nice_sink_finalize; gstelement_class = (GstElementClass *) klass; gstelement_class->change_state = gst_nice_sink_change_state; gst_element_class_add_pad_template (gstelement_class, gst_static_pad_template_get (&gst_nice_sink_sink_template)); gst_element_class_set_metadata (gstelement_class, "ICE sink", "Sink", "Interactive UDP connectivity establishment", "Dafydd Harries "); g_object_class_install_property (gobject_class, PROP_AGENT, g_param_spec_object ( "agent", "Agent", "The NiceAgent this source is bound to", NICE_TYPE_AGENT, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_STREAM, g_param_spec_uint ( "stream", "Stream ID", "The ID of the stream to read from", 0, G_MAXUINT, 0, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_COMPONENT, g_param_spec_uint ( "component", "Component ID", "The ID of the component to read from", 0, G_MAXUINT, 0, G_PARAM_READWRITE)); } static void gst_nice_sink_init (GstNiceSink *sink) { guint max_mem; g_cond_init (&sink->writable_cond); /* pre-allocate OutputVector, MapInfo and OutputMessage arrays * for use in the render and render_list functions */ max_mem = gst_buffer_get_max_memory (); sink->n_vecs = max_mem; sink->vecs = g_new (GOutputVector, sink->n_vecs); sink->n_maps = max_mem; sink->maps = g_new (GstMapInfo, sink->n_maps); sink->n_messages = 1; sink->messages = g_new (NiceOutputMessage, sink->n_messages); #if GST_CHECK_VERSION (1,12,0) gst_base_sink_set_drop_out_of_segment (GST_BASE_SINK (sink), FALSE); #endif } static void _reliable_transport_writable (NiceAgent *agent, guint stream_id, guint component_id, GstNiceSink *sink) { GST_OBJECT_LOCK (sink); if (stream_id == sink->stream_id && component_id == sink->component_id) { g_cond_broadcast (&sink->writable_cond); } GST_OBJECT_UNLOCK (sink); } static gsize fill_vectors (GOutputVector * vecs, GstMapInfo * maps, guint n, GstBuffer * buf) { GstMemory *mem; gsize size = 0; guint i; g_assert (gst_buffer_n_memory (buf) == n); for (i = 0; i < n; ++i) { mem = gst_buffer_peek_memory (buf, i); if (gst_memory_map (mem, &maps[i], GST_MAP_READ)) { vecs[i].buffer = maps[i].data; vecs[i].size = maps[i].size; } else { GST_WARNING ("Failed to map memory %p for reading", mem); vecs[i].buffer = ""; vecs[i].size = 0; } size += vecs[i].size; } return size; } /* Buffer list code written by * Tim-Philipp Müller * taken from * gst-plugins-good/gst/udp/gstmultiudpsink.c */ static GstFlowReturn gst_nice_sink_render_buffers (GstNiceSink * sink, GstBuffer ** buffers, guint num_buffers, guint8 * mem_nums, guint total_mem_num) { NiceOutputMessage *msgs; GOutputVector *vecs; GstMapInfo *map_infos; guint i, mem; guint written = 0; gint ret; GstFlowReturn flow_ret = GST_FLOW_OK; GST_LOG_OBJECT (sink, "%u buffers, %u memories -> to be sent", num_buffers, total_mem_num); if (sink->n_vecs < total_mem_num) { sink->n_vecs = GST_ROUND_UP_16 (total_mem_num); g_free (sink->vecs); sink->vecs = g_new (GOutputVector, sink->n_vecs); } vecs = sink->vecs; if (sink->n_maps < total_mem_num) { sink->n_maps = GST_ROUND_UP_16 (total_mem_num); g_free (sink->maps); sink->maps = g_new (GstMapInfo, sink->n_maps); } map_infos = sink->maps; if (sink->n_messages < num_buffers) { sink->n_messages = GST_ROUND_UP_16 (num_buffers); g_free (sink->messages); sink->messages = g_new (NiceOutputMessage, sink->n_messages); } msgs = sink->messages; for (i = 0, mem = 0; i < num_buffers; ++i) { fill_vectors (&vecs[mem], &map_infos[mem], mem_nums[i], buffers[i]); msgs[i].buffers = &vecs[mem]; msgs[i].n_buffers = mem_nums[i]; mem += mem_nums[i]; } GST_OBJECT_LOCK (sink); do { ret = nice_agent_send_messages_nonblocking(sink->agent, sink->stream_id, sink->component_id, msgs + written, num_buffers - written, NULL, NULL); if (ret > 0) written += ret; if (sink->reliable && written < num_buffers) g_cond_wait (&sink->writable_cond, GST_OBJECT_GET_LOCK (sink)); if (sink->flushing) { flow_ret = GST_FLOW_FLUSHING; break; } } while (sink->reliable && written < num_buffers); GST_OBJECT_UNLOCK (sink); for (i = 0; i < mem; ++i) gst_memory_unmap (map_infos[i].memory, &map_infos[i]); return flow_ret; } static GstFlowReturn gst_nice_sink_render (GstBaseSink *basesink, GstBuffer *buffer) { GstNiceSink *nicesink = GST_NICE_SINK (basesink); GstFlowReturn flow_ret = GST_FLOW_OK; guint8 n_mem; n_mem = gst_buffer_n_memory (buffer); if (n_mem > 0) { flow_ret = gst_nice_sink_render_buffers (nicesink, &buffer, 1, &n_mem, n_mem); } return flow_ret; } static GstFlowReturn gst_nice_sink_render_list (GstBaseSink *basesink, GstBufferList *buffer_list) { GstNiceSink *nicesink = GST_NICE_SINK (basesink); GstBuffer **buffers; GstFlowReturn flow_ret = GST_FLOW_OK; guint8 *mem_nums; guint total_mems; guint i, num_buffers; num_buffers = gst_buffer_list_length (buffer_list); if (num_buffers == 0) goto no_data; buffers = g_newa (GstBuffer *, num_buffers); mem_nums = g_newa (guint8, num_buffers); for (i = 0, total_mems = 0; i < num_buffers; ++i) { buffers[i] = gst_buffer_list_get (buffer_list, i); mem_nums[i] = gst_buffer_n_memory (buffers[i]); total_mems += mem_nums[i]; } flow_ret = gst_nice_sink_render_buffers (nicesink, buffers, num_buffers, mem_nums, total_mems); return flow_ret; no_data: { GST_LOG_OBJECT (nicesink, "empty buffer"); return GST_FLOW_OK; } return flow_ret; } static gboolean gst_nice_sink_unlock (GstBaseSink *basesink) { GstNiceSink *nicesink = GST_NICE_SINK (basesink); GST_OBJECT_LOCK (nicesink); nicesink->flushing = TRUE; g_cond_broadcast (&nicesink->writable_cond); GST_OBJECT_UNLOCK (nicesink); return TRUE; } static gboolean gst_nice_sink_unlock_stop (GstBaseSink *basesink) { GstNiceSink *nicesink = GST_NICE_SINK (basesink); GST_OBJECT_LOCK (nicesink); nicesink->flushing = FALSE; GST_OBJECT_UNLOCK (nicesink); return TRUE; } static void gst_nice_sink_dispose (GObject *object) { GstNiceSink *sink = GST_NICE_SINK (object); if (sink->agent && sink->writable_id) g_signal_handler_disconnect (sink->agent, sink->writable_id); sink->writable_id = 0; g_clear_object (&sink->agent); g_cond_clear (&sink->writable_cond); G_OBJECT_CLASS (gst_nice_sink_parent_class)->dispose (object); } static void gst_nice_sink_finalize (GObject *object) { GstNiceSink *sink = GST_NICE_SINK (object); g_free (sink->vecs); sink->vecs = NULL; sink->n_vecs = 0; g_free (sink->maps); sink->maps = NULL; sink->n_maps = 0; g_free (sink->messages); sink->messages = NULL; sink->n_messages = 0; G_OBJECT_CLASS (gst_nice_sink_parent_class)->finalize (object); } static void gst_nice_sink_set_property ( GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GstNiceSink *sink = GST_NICE_SINK (object); switch (prop_id) { case PROP_AGENT: if (sink->agent) { GST_ERROR_OBJECT (object, "Changing the agent on a nice sink not allowed"); } else { sink->agent = g_value_dup_object (value); g_object_get (sink->agent, "reliable", &sink->reliable, NULL); if (sink->reliable) sink->writable_id = g_signal_connect (sink->agent, "reliable-transport-writable", (GCallback) _reliable_transport_writable, sink); } break; case PROP_STREAM: GST_OBJECT_LOCK (sink); sink->stream_id = g_value_get_uint (value); GST_OBJECT_UNLOCK (sink); break; case PROP_COMPONENT: { guint new_component_id = g_value_get_uint (value); GST_OBJECT_LOCK (sink); if (sink->component_id != new_component_id) { sink->component_id = new_component_id; g_cond_broadcast (&sink->writable_cond); } GST_OBJECT_UNLOCK (sink); } break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_nice_sink_get_property ( GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GstNiceSink *sink = GST_NICE_SINK (object); switch (prop_id) { case PROP_AGENT: g_value_set_object (value, sink->agent); break; case PROP_STREAM: GST_OBJECT_LOCK (sink); g_value_set_uint (value, sink->stream_id); GST_OBJECT_UNLOCK (sink); break; case PROP_COMPONENT: GST_OBJECT_LOCK (sink); g_value_set_uint (value, sink->component_id); GST_OBJECT_UNLOCK (sink); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static GstStateChangeReturn gst_nice_sink_change_state (GstElement * element, GstStateChange transition) { GstNiceSink *sink; GstStateChangeReturn ret; sink = GST_NICE_SINK (element); switch (transition) { case GST_STATE_CHANGE_NULL_TO_READY: if (sink->agent == NULL) { GST_ERROR_OBJECT (element, "Trying to start Nice sink without an agent set"); return GST_STATE_CHANGE_FAILURE; } else if (sink->stream_id == 0) { GST_ERROR_OBJECT (element, "Trying to start Nice sink without a stream set"); return GST_STATE_CHANGE_FAILURE; } else if (sink->component_id == 0) { GST_ERROR_OBJECT (element, "Trying to start Nice sink without a component set"); return GST_STATE_CHANGE_FAILURE; } break; case GST_STATE_CHANGE_READY_TO_PAUSED: case GST_STATE_CHANGE_PAUSED_TO_PLAYING: case GST_STATE_CHANGE_PLAYING_TO_PAUSED: case GST_STATE_CHANGE_PAUSED_TO_READY: case GST_STATE_CHANGE_READY_TO_NULL: default: break; } ret = GST_ELEMENT_CLASS (gst_nice_sink_parent_class)->change_state (element, transition); return ret; } gboolean gst_element_register_nicesink (GstPlugin * plugin) { return gst_element_register (plugin, "nicesink", GST_RANK_NONE, GST_TYPE_NICE_SINK); }