From: Thomas Vander Stichele Date: Thu, 26 Jan 2012 22:19:33 +0000 (+0100) Subject: multihandlesink: further refactoring X-Git-Tag: 1.19.3~511^2~6805 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=604ddc8740a873733c0ac4dcde4d134481cd399e;p=platform%2Fupstream%2Fgstreamer.git multihandlesink: further refactoring --- diff --git a/gst/tcp/gstmultifdsink.c b/gst/tcp/gstmultifdsink.c index 5257d4e..b78a99b 100644 --- a/gst/tcp/gstmultifdsink.c +++ b/gst/tcp/gstmultifdsink.c @@ -166,15 +166,10 @@ enum #define DEFAULT_QOS_DSCP -1 #define DEFAULT_HANDLE_READ TRUE -#define DEFAULT_RESEND_STREAMHEADER TRUE - enum { PROP_0, PROP_MODE, - PROP_BUFFERS_QUEUED, - PROP_BYTES_QUEUED, - PROP_TIME_QUEUED, PROP_UNIT_TYPE, PROP_UNITS_MAX, @@ -190,8 +185,6 @@ enum PROP_HANDLE_READ, - PROP_RESEND_STREAMHEADER, - PROP_NUM_FDS, PROP_LAST @@ -238,13 +231,17 @@ gst_unit_type_get_type (void) static void gst_multi_fd_sink_finalize (GObject * object); -static void gst_multi_fd_sink_remove_client_link (GstMultiFdSink * sink, +static void gst_multi_fd_sink_clear_post (GstMultiHandleSink * mhsink); +static void gst_multi_fd_sink_stop_pre (GstMultiHandleSink * mhsink); +static void gst_multi_fd_sink_stop_post (GstMultiHandleSink * mhsink); +static gboolean gst_multi_fd_sink_start_pre (GstMultiHandleSink * mhsink); +static gpointer gst_multi_fd_sink_thread (GstMultiHandleSink * mhsink); + +static void gst_multi_fd_sink_remove_client_link (GstMultiHandleSink * sink, GList * link); static GstFlowReturn gst_multi_fd_sink_render (GstBaseSink * bsink, GstBuffer * buf); -static GstStateChangeReturn gst_multi_fd_sink_change_state (GstElement * - element, GstStateChange transition); static void gst_multi_fd_sink_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); @@ -313,21 +310,6 @@ gst_multi_fd_sink_class_init (GstMultiFdSinkClass * klass) G_MAXINT64, DEFAULT_UNITS_SOFT_MAX, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - g_object_class_install_property (gobject_class, PROP_BUFFERS_QUEUED, - g_param_spec_uint ("buffers-queued", "Buffers queued", - "Number of buffers currently queued", 0, G_MAXUINT, 0, - G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); -#if NOT_IMPLEMENTED - g_object_class_install_property (gobject_class, PROP_BYTES_QUEUED, - g_param_spec_uint ("bytes-queued", "Bytes queued", - "Number of bytes currently queued", 0, G_MAXUINT, 0, - G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); - g_object_class_install_property (gobject_class, PROP_TIME_QUEUED, - g_param_spec_uint64 ("time-queued", "Time queued", - "Number of time currently queued", 0, G_MAXUINT64, 0, - G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); -#endif - g_object_class_install_property (gobject_class, PROP_BURST_UNIT, g_param_spec_enum ("burst-unit", "Burst unit", "The format of the burst units (when sync-method is burst[[-with]-keyframe])", @@ -354,18 +336,6 @@ gst_multi_fd_sink_class_init (GstMultiFdSinkClass * klass) g_param_spec_boolean ("handle-read", "Handle Read", "Handle client reads and discard the data", DEFAULT_HANDLE_READ, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - /** - * GstMultiFdSink::resend-streamheader - * - * Resend the streamheaders to existing clients when they change. - * - * Since: 0.10.23 - */ - g_object_class_install_property (gobject_class, PROP_RESEND_STREAMHEADER, - g_param_spec_boolean ("resend-streamheader", "Resend streamheader", - "Resend the streamheader if it changes in the caps", - DEFAULT_RESEND_STREAMHEADER, - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_NUM_FDS, g_param_spec_uint ("num-fds", "Number of fds", @@ -516,12 +486,23 @@ gst_multi_fd_sink_class_init (GstMultiFdSinkClass * klass) "Thomas Vander Stichele , " "Wim Taymans "); - gstelement_class->change_state = - GST_DEBUG_FUNCPTR (gst_multi_fd_sink_change_state); - gstbasesink_class->render = GST_DEBUG_FUNCPTR (gst_multi_fd_sink_render); - gstmultihandlesink_class->clear = GST_DEBUG_FUNCPTR (gst_multi_fd_sink_clear); + gstmultihandlesink_class->clear_post = + GST_DEBUG_FUNCPTR (gst_multi_fd_sink_clear_post); + + gstmultihandlesink_class->stop_pre = + GST_DEBUG_FUNCPTR (gst_multi_fd_sink_stop_pre); + gstmultihandlesink_class->stop_post = + GST_DEBUG_FUNCPTR (gst_multi_fd_sink_stop_post); + gstmultihandlesink_class->start_pre = + GST_DEBUG_FUNCPTR (gst_multi_fd_sink_start_pre); + gstmultihandlesink_class->thread = + GST_DEBUG_FUNCPTR (gst_multi_fd_sink_thread); + + gstmultihandlesink_class->remove_client_link = + GST_DEBUG_FUNCPTR (gst_multi_fd_sink_remove_client_link); + klass->add = GST_DEBUG_FUNCPTR (gst_multi_fd_sink_add); klass->add_full = GST_DEBUG_FUNCPTR (gst_multi_fd_sink_add_full); @@ -535,15 +516,10 @@ gst_multi_fd_sink_class_init (GstMultiFdSinkClass * klass) static void gst_multi_fd_sink_init (GstMultiFdSink * this) { - GST_OBJECT_FLAG_UNSET (this, GST_MULTI_HANDLE_SINK_OPEN); - this->mode = DEFAULT_MODE; - CLIENTS_LOCK_INIT (this); - this->clients = NULL; this->fd_hash = g_hash_table_new (g_int_hash, g_int_equal); - this->bufqueue = g_array_new (FALSE, TRUE, sizeof (GstBuffer *)); this->unit_type = DEFAULT_UNIT_TYPE; this->units_max = DEFAULT_UNITS_MAX; this->units_soft_max = DEFAULT_UNITS_SOFT_MAX; @@ -554,21 +530,15 @@ gst_multi_fd_sink_init (GstMultiFdSink * this) this->qos_dscp = DEFAULT_QOS_DSCP; this->handle_read = DEFAULT_HANDLE_READ; - this->resend_streamheader = DEFAULT_RESEND_STREAMHEADER; - this->header_flags = 0; } static void gst_multi_fd_sink_finalize (GObject * object) { - GstMultiFdSink *this; - - this = GST_MULTI_FD_SINK (object); + GstMultiFdSink *this = GST_MULTI_FD_SINK (object); - CLIENTS_LOCK_CLEAR (this); g_hash_table_destroy (this->fd_hash); - g_array_free (this->bufqueue, TRUE); G_OBJECT_CLASS (parent_class)->finalize (object); } @@ -638,9 +608,10 @@ static void setup_dscp (GstMultiFdSink * sink) { GList *clients, *next; + GstMultiHandleSink *mhsink = GST_MULTI_HANDLE_SINK (sink); CLIENTS_LOCK (sink); - for (clients = sink->clients; clients; clients = next) { + for (clients = mhsink->clients; clients; clients = next) { GstTCPClient *client; client = (GstTCPClient *) clients->data; @@ -662,6 +633,7 @@ gst_multi_fd_sink_add_full (GstMultiFdSink * sink, int fd, GList *clink; gint flags; struct stat statbuf; + GstMultiHandleSink *mhsink = GST_MULTI_HANDLE_SINK (sink); GST_DEBUG_OBJECT (sink, "[fd %5d] adding client, sync_method %d, " "min_unit %d, min_value %" G_GUINT64_FORMAT @@ -678,6 +650,7 @@ gst_multi_fd_sink_add_full (GstMultiFdSink * sink, int fd, client = g_new0 (GstTCPClient, 1); mhclient = (GstMultiHandleClient *) client; gst_multi_handle_sink_client_init (mhclient, sync_method); + g_snprintf (mhclient->debug, 30, "[fd %5d]", fd); client->fd.fd = fd; client->burst_min_unit = min_unit; @@ -693,9 +666,9 @@ gst_multi_fd_sink_add_full (GstMultiFdSink * sink, int fd, goto duplicate; /* we can add the fd now */ - clink = sink->clients = g_list_prepend (sink->clients, client); + clink = mhsink->clients = g_list_prepend (mhsink->clients, client); g_hash_table_insert (sink->fd_hash, &client->fd.fd, clink); - sink->clients_cookie++; + mhsink->clients_cookie++; /* set the socket to non blocking */ if (fcntl (fd, F_SETFL, O_NONBLOCK) < 0) { @@ -766,6 +739,9 @@ void gst_multi_fd_sink_remove (GstMultiFdSink * sink, int fd) { GList *clink; + GstMultiHandleSink *mhsink = GST_MULTI_HANDLE_SINK (sink); + GstMultiHandleSinkClass *mhsinkclass = + GST_MULTI_HANDLE_SINK_GET_CLASS (mhsink); GST_DEBUG_OBJECT (sink, "[fd %5d] removing client", fd); @@ -783,7 +759,8 @@ gst_multi_fd_sink_remove (GstMultiFdSink * sink, int fd) } mhclient->status = GST_CLIENT_STATUS_REMOVED; - gst_multi_fd_sink_remove_client_link (sink, clink); + mhsinkclass->remove_client_link (GST_MULTI_HANDLE_SINK (sink), clink); + // FIXME: specific poll gst_poll_restart (sink->fdset); } else { GST_WARNING_OBJECT (sink, "[fd %5d] no client with this fd found!", fd); @@ -828,39 +805,13 @@ done: CLIENTS_UNLOCK (sink); } -/* can be called both through the signal (i.e. from any thread) or when - * stopping, after the writing thread has shut down */ -void -gst_multi_fd_sink_clear (GstMultiHandleSink * mhsink) +/* called with the CLIENTS_LOCK held */ +static void +gst_multi_fd_sink_clear_post (GstMultiHandleSink * mhsink) { - GList *clients, *next; - guint32 cookie; GstMultiFdSink *sink = GST_MULTI_FD_SINK (mhsink); - - GST_DEBUG_OBJECT (sink, "clearing all clients"); - - CLIENTS_LOCK (sink); -restart: - cookie = sink->clients_cookie; - for (clients = sink->clients; clients; clients = next) { - GstMultiHandleClient *mhclient; - - if (cookie != sink->clients_cookie) { - GST_DEBUG_OBJECT (sink, "cookie changed while removing all clients"); - goto restart; - } - - mhclient = (GstMultiHandleClient *) clients->data; - next = g_list_next (clients); - - mhclient->status = GST_CLIENT_STATUS_REMOVED; - /* the next call changes the list, which is why we iterate - * with a temporary next pointer */ - gst_multi_fd_sink_remove_client_link (sink, clients); - } gst_poll_restart (sink->fdset); - CLIENTS_UNLOCK (sink); } /* "get-stats" signal implementation @@ -955,12 +906,13 @@ noclient: * close the fd itself. */ static void -gst_multi_fd_sink_remove_client_link (GstMultiFdSink * sink, GList * link) +gst_multi_fd_sink_remove_client_link (GstMultiHandleSink * sink, GList * link) { int fd; GTimeVal now; GstTCPClient *client = (GstTCPClient *) link->data; GstMultiHandleClient *mhclient = (GstMultiHandleClient *) client; + GstMultiFdSink *mfsink = GST_MULTI_FD_SINK (sink); GstMultiFdSinkClass *fclass; fclass = GST_MULTI_FD_SINK_GET_CLASS (sink); @@ -968,7 +920,8 @@ gst_multi_fd_sink_remove_client_link (GstMultiFdSink * sink, GList * link) fd = client->fd.fd; if (mhclient->currently_removing) { - GST_WARNING_OBJECT (sink, "[fd %5d] client is already being removed", fd); + GST_WARNING_OBJECT (sink, "%s client is already being removed", + mhclient->debug); return; } else { mhclient->currently_removing = TRUE; @@ -977,34 +930,36 @@ gst_multi_fd_sink_remove_client_link (GstMultiFdSink * sink, GList * link) /* FIXME: if we keep track of ip we can log it here and signal */ switch (mhclient->status) { case GST_CLIENT_STATUS_OK: - GST_WARNING_OBJECT (sink, "[fd %5d] removing client %p for no reason", - fd, client); + GST_WARNING_OBJECT (sink, "%s removing client %p for no reason", + mhclient->debug, client); break; case GST_CLIENT_STATUS_CLOSED: - GST_DEBUG_OBJECT (sink, "[fd %5d] removing client %p because of close", - fd, client); + GST_DEBUG_OBJECT (sink, "%s removing client %p because of close", + mhclient->debug, client); break; case GST_CLIENT_STATUS_REMOVED: GST_DEBUG_OBJECT (sink, - "[fd %5d] removing client %p because the app removed it", fd, client); + "%s removing client %p because the app removed it", mhclient->debug, + client); break; case GST_CLIENT_STATUS_SLOW: GST_INFO_OBJECT (sink, - "[fd %5d] removing client %p because it was too slow", fd, client); + "%s removing client %p because it was too slow", mhclient->debug, + client); break; case GST_CLIENT_STATUS_ERROR: GST_WARNING_OBJECT (sink, - "[fd %5d] removing client %p because of error", fd, client); + "%s removing client %p because of error", mhclient->debug, client); break; case GST_CLIENT_STATUS_FLUSHING: default: GST_WARNING_OBJECT (sink, - "[fd %5d] removing client %p with invalid reason %d", fd, client, - mhclient->status); + "%s removing client %p with invalid reason %d", mhclient->debug, + client, mhclient->status); break; } - gst_poll_remove_fd (sink->fdset, &client->fd); + gst_poll_remove_fd (mfsink->fdset, &client->fd); g_get_current_time (&now); mhclient->disconnect_time = GST_TIMEVAL_TO_TIME (now); @@ -1031,7 +986,7 @@ gst_multi_fd_sink_remove_client_link (GstMultiFdSink * sink, GList * link) /* fd cannot be reused in the above signal callback so we can safely * remove it from the hashtable here */ - if (!g_hash_table_remove (sink->fd_hash, &client->fd.fd)) { + if (!g_hash_table_remove (mfsink->fd_hash, &client->fd.fd)) { GST_WARNING_OBJECT (sink, "[fd %5d] error removing client %p from hash", client->fd.fd, client); } @@ -1044,7 +999,7 @@ gst_multi_fd_sink_remove_client_link (GstMultiFdSink * sink, GList * link) sink->clients_cookie++; if (fclass->removed) - fclass->removed (sink, client->fd.fd); + fclass->removed (mfsink, client->fd.fd); g_free (client); CLIENTS_UNLOCK (sink); @@ -1129,18 +1084,6 @@ ioctl_failed: } } -static gboolean -is_sync_frame (GstMultiFdSink * sink, GstBuffer * buffer) -{ - if (GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT)) { - return FALSE; - } else if (!GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_IN_CAPS)) { - return TRUE; - } - - return FALSE; -} - /* queue the given buffer for the given client */ static gboolean gst_multi_fd_sink_client_queue_buffer (GstMultiFdSink * sink, @@ -1152,6 +1095,7 @@ gst_multi_fd_sink_client_queue_buffer (GstMultiFdSink * sink, gboolean send_streamheader = FALSE; GstStructure *s; GstMultiHandleClient *mhclient = (GstMultiHandleClient *) client; + GstMultiHandleSink *mhsink = GST_MULTI_HANDLE_SINK (sink); /* before we queue the buffer, we check if we need to queue streamheader * buffers (because it's a new client, or because they changed) */ @@ -1186,7 +1130,7 @@ gst_multi_fd_sink_client_queue_buffer (GstMultiFdSink * sink, send_streamheader = TRUE; } else { /* both old and new caps have streamheader set */ - if (!sink->resend_streamheader) { + if (!mhsink->resend_streamheader) { GST_DEBUG_OBJECT (sink, "[fd %5d] asked to not resend the streamheader, not sending", client->fd.fd); @@ -1259,40 +1203,6 @@ gst_multi_fd_sink_client_queue_buffer (GstMultiFdSink * sink, return TRUE; } -/* find the keyframe in the list of buffers starting the - * search from @idx. @direction as -1 will search backwards, - * 1 will search forwards. - * Returns: the index or -1 if there is no keyframe after idx. - */ -static gint -find_syncframe (GstMultiFdSink * sink, gint idx, gint direction) -{ - gint i, len, result; - - /* take length of queued buffers */ - len = sink->bufqueue->len; - - /* assume we don't find a keyframe */ - result = -1; - - /* then loop over all buffers to find the first keyframe */ - for (i = idx; i >= 0 && i < len; i += direction) { - GstBuffer *buf; - - buf = g_array_index (sink->bufqueue, GstBuffer *, i); - if (is_sync_frame (sink, buf)) { - GST_LOG_OBJECT (sink, "found keyframe at %d from %d, direction %d", - i, idx, direction); - result = i; - break; - } - } - return result; -} - -#define find_next_syncframe(s,i) find_syncframe(s,i,1) -#define find_prev_syncframe(s,i) find_syncframe(s,i,-1) - /* Get the number of buffers from the buffer queue needed to satisfy * the maximum max in the configured units. * If units are not BUFFERS, and there are insufficient buffers in the @@ -1300,6 +1210,8 @@ find_syncframe (GstMultiFdSink * sink, gint idx, gint direction) static gint get_buffers_max (GstMultiFdSink * sink, gint64 max) { + GstMultiHandleSink *mhsink = GST_MULTI_HANDLE_SINK (sink); + switch (sink->unit_type) { case GST_TCP_UNIT_TYPE_BUFFERS: return max; @@ -1311,10 +1223,10 @@ get_buffers_max (GstMultiFdSink * sink, gint64 max) gint64 diff; GstClockTime first = GST_CLOCK_TIME_NONE; - len = sink->bufqueue->len; + len = mhsink->bufqueue->len; for (i = 0; i < len; i++) { - buf = g_array_index (sink->bufqueue, GstBuffer *, i); + buf = g_array_index (mhsink->bufqueue, GstBuffer *, i); if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) { if (first == -1) first = GST_BUFFER_TIMESTAMP (buf); @@ -1334,10 +1246,10 @@ get_buffers_max (GstMultiFdSink * sink, gint64 max) int len; gint acc = 0; - len = sink->bufqueue->len; + len = mhsink->bufqueue->len; for (i = 0; i < len; i++) { - buf = g_array_index (sink->bufqueue, GstBuffer *, i); + buf = g_array_index (mhsink->bufqueue, GstBuffer *, i); acc += gst_buffer_get_size (buf); if (acc > max) @@ -1370,9 +1282,10 @@ find_limits (GstMultiFdSink * sink, GstClockTime first, time; gint i, len, bytes; gboolean result, max_hit; + GstMultiHandleSink *mhsink = GST_MULTI_HANDLE_SINK (sink); /* take length of queue */ - len = sink->bufqueue->len; + len = mhsink->bufqueue->len; /* this must hold */ g_assert (len > 0); @@ -1419,7 +1332,7 @@ find_limits (GstMultiFdSink * sink, result = *min_idx != -1; break; } - buf = g_array_index (sink->bufqueue, GstBuffer *, i); + buf = g_array_index (mhsink->bufqueue, GstBuffer *, i); bytes += gst_buffer_get_size (buf); @@ -1531,11 +1444,12 @@ gst_multi_fd_sink_new_client (GstMultiFdSink * sink, GstTCPClient * client) { gint result; GstMultiHandleClient *mhclient = (GstMultiHandleClient *) client; + GstMultiHandleSink *mhsink = GST_MULTI_HANDLE_SINK (sink); GST_DEBUG_OBJECT (sink, "[fd %5d] new client, deciding where to start in queue", client->fd.fd); GST_DEBUG_OBJECT (sink, "queue is currently %d buffers long", - sink->bufqueue->len); + mhsink->bufqueue->len); switch (mhclient->sync_method) { case GST_SYNC_METHOD_LATEST: /* no syncing, we are happy with whatever the client is going to get */ @@ -1551,7 +1465,7 @@ gst_multi_fd_sink_new_client (GstMultiFdSink * sink, GstTCPClient * client) "[fd %5d] new client, bufpos %d, waiting for keyframe", client->fd.fd, mhclient->bufpos); - result = find_prev_syncframe (sink, mhclient->bufpos); + result = find_prev_syncframe (mhsink, mhclient->bufpos); if (result != -1) { GST_DEBUG_OBJECT (sink, "[fd %5d] SYNC_METHOD_NEXT_KEYFRAME: result %d", @@ -1577,7 +1491,7 @@ gst_multi_fd_sink_new_client (GstMultiFdSink * sink, GstTCPClient * client) * we need to wait for the next keyframe and so we change the client's * sync method to GST_SYNC_METHOD_NEXT_KEYFRAME. */ - result = find_next_syncframe (sink, 0); + result = find_next_syncframe (mhsink, 0); if (result != -1) { GST_DEBUG_OBJECT (sink, "[fd %5d] SYNC_METHOD_LATEST_KEYFRAME: result %d", client->fd.fd, @@ -1642,7 +1556,7 @@ gst_multi_fd_sink_new_client (GstMultiFdSink * sink, GstTCPClient * client) GST_LOG_OBJECT (sink, "min %d, max %d", min_idx, max_idx); /* first find a keyframe after min_idx */ - next_syncframe = find_next_syncframe (sink, min_idx); + next_syncframe = find_next_syncframe (mhsink, min_idx); if (next_syncframe != -1 && next_syncframe < max_idx) { /* we have a valid keyframe and it's below the max */ GST_LOG_OBJECT (sink, "found keyframe in min/max limits"); @@ -1651,7 +1565,7 @@ gst_multi_fd_sink_new_client (GstMultiFdSink * sink, GstTCPClient * client) } /* no valid keyframe, try to find one below min */ - prev_syncframe = find_prev_syncframe (sink, min_idx); + prev_syncframe = find_prev_syncframe (mhsink, min_idx); if (prev_syncframe != -1) { GST_WARNING_OBJECT (sink, "using keyframe below min in BURST_KEYFRAME sync mode"); @@ -1689,7 +1603,7 @@ gst_multi_fd_sink_new_client (GstMultiFdSink * sink, GstTCPClient * client) GST_LOG_OBJECT (sink, "min %d, max %d", min_idx, max_idx); /* first find a keyframe after min_idx */ - next_syncframe = find_next_syncframe (sink, min_idx); + next_syncframe = find_next_syncframe (mhsink, min_idx); if (next_syncframe != -1 && next_syncframe < max_idx) { /* we have a valid keyframe and it's below the max */ GST_LOG_OBJECT (sink, "found keyframe in min/max limits"); @@ -1799,7 +1713,7 @@ gst_multi_fd_sink_handle_client_write (GstMultiFdSink * sink, goto flushed; /* grab buffer */ - buf = g_array_index (sink->bufqueue, GstBuffer *, mhclient->bufpos); + buf = g_array_index (mhsink->bufqueue, GstBuffer *, mhclient->bufpos); mhclient->bufpos--; /* update stats */ @@ -1941,14 +1855,14 @@ gst_multi_fd_sink_recover_client (GstMultiFdSink * sink, GstTCPClient * client) case GST_RECOVER_POLICY_RESYNC_KEYFRAME: /* find keyframe in buffers, we search backwards to find the * closest keyframe relative to what this client already received. */ - newbufpos = MIN (sink->bufqueue->len - 1, + newbufpos = MIN (mhsink->bufqueue->len - 1, get_buffers_max (sink, sink->units_soft_max) - 1); while (newbufpos >= 0) { GstBuffer *buf; - buf = g_array_index (sink->bufqueue, GstBuffer *, newbufpos); - if (is_sync_frame (sink, buf)) { + buf = g_array_index (mhsink->bufqueue, GstBuffer *, newbufpos); + if (is_sync_frame (mhsink, buf)) { /* found a buffer that is not a delta unit */ break; } @@ -1993,17 +1907,17 @@ gst_multi_fd_sink_queue_buffer (GstMultiFdSink * sink, GstBuffer * buf) GstClockTime now; gint max_buffers, soft_max_buffers; guint cookie; - GstMultiHandleSink *mhsink; - - mhsink = GST_MULTI_HANDLE_SINK (sink); + GstMultiHandleSink *mhsink = GST_MULTI_HANDLE_SINK (sink); + GstMultiHandleSinkClass *mhsinkclass = + GST_MULTI_HANDLE_SINK_GET_CLASS (mhsink); g_get_current_time (&nowtv); now = GST_TIMEVAL_TO_TIME (nowtv); CLIENTS_LOCK (sink); /* add buffer to queue */ - g_array_prepend_val (sink->bufqueue, buf); - queuelen = sink->bufqueue->len; + g_array_prepend_val (mhsink->bufqueue, buf); + queuelen = mhsink->bufqueue->len; if (sink->units_max > 0) max_buffers = get_buffers_max (sink, sink->units_max); @@ -2021,12 +1935,12 @@ gst_multi_fd_sink_queue_buffer (GstMultiFdSink * sink, GstBuffer * buf) max_buffer_usage = 0; restart: - cookie = sink->clients_cookie; - for (clients = sink->clients; clients; clients = next) { + cookie = mhsink->clients_cookie; + for (clients = mhsink->clients; clients; clients = next) { GstTCPClient *client; GstMultiHandleClient *mhclient; - if (cookie != sink->clients_cookie) { + if (cookie != mhsink->clients_cookie) { GST_DEBUG_OBJECT (sink, "Clients cookie outdated, restarting"); goto restart; } @@ -2067,7 +1981,7 @@ restart: mhclient->status = GST_CLIENT_STATUS_SLOW; /* set client to invalid position while being removed */ mhclient->bufpos = -1; - gst_multi_fd_sink_remove_client_link (sink, clients); + mhsinkclass->remove_client_link (mhsink, clients); need_signal = TRUE; continue; } else if (mhclient->bufpos == 0 || mhclient->new_connection) { @@ -2119,8 +2033,8 @@ restart: "extending queue to include sync point, now at %d, limit is %d", max_buffer_usage, limit); for (i = 0; i < limit; i++) { - buf = g_array_index (sink->bufqueue, GstBuffer *, i); - if (is_sync_frame (sink, buf)) { + buf = g_array_index (mhsink->bufqueue, GstBuffer *, i); + if (is_sync_frame (mhsink, buf)) { /* found a sync frame, now extend the buffer usage to * include at least this frame. */ max_buffer_usage = MAX (max_buffer_usage, i); @@ -2140,14 +2054,14 @@ restart: /* queue exceeded max size */ queuelen--; - old = g_array_index (sink->bufqueue, GstBuffer *, i); - sink->bufqueue = g_array_remove_index (sink->bufqueue, i); + old = g_array_index (mhsink->bufqueue, GstBuffer *, i); + mhsink->bufqueue = g_array_remove_index (mhsink->bufqueue, i); /* unref tail buffer */ gst_buffer_unref (old); } /* save for stats */ - sink->buffers_queued = max_buffer_usage; + mhsink->buffers_queued = max_buffer_usage; CLIENTS_UNLOCK (sink); /* and send a signal to thread if fd_set changed */ @@ -2172,9 +2086,10 @@ gst_multi_fd_sink_handle_clients (GstMultiFdSink * sink) gboolean try_again; GstMultiFdSinkClass *fclass; guint cookie; - GstMultiHandleSink *mhsink; + GstMultiHandleSink *mhsink = GST_MULTI_HANDLE_SINK (sink); + GstMultiHandleSinkClass *mhsinkclass = + GST_MULTI_HANDLE_SINK_GET_CLASS (mhsink); - mhsink = GST_MULTI_HANDLE_SINK (sink); fclass = GST_MULTI_FD_SINK_GET_CLASS (sink); @@ -2201,7 +2116,7 @@ gst_multi_fd_sink_handle_clients (GstMultiFdSink * sink) now = GST_TIMEVAL_TO_TIME (nowtv); CLIENTS_LOCK (sink); - for (clients = sink->clients; clients; clients = next) { + for (clients = mhsink->clients; clients; clients = next) { GstTCPClient *client; GstMultiHandleClient *mhclient; @@ -2211,7 +2126,7 @@ gst_multi_fd_sink_handle_clients (GstMultiFdSink * sink) if (mhsink->timeout > 0 && now - mhclient->last_activity_time > mhsink->timeout) { mhclient->status = GST_CLIENT_STATUS_SLOW; - gst_multi_fd_sink_remove_client_link (sink, clients); + mhsinkclass->remove_client_link (mhsink, clients); } } CLIENTS_UNLOCK (sink); @@ -2224,15 +2139,15 @@ gst_multi_fd_sink_handle_clients (GstMultiFdSink * sink) * the ones that give an error to the F_GETFL fcntl. */ CLIENTS_LOCK (sink); restart: - cookie = sink->clients_cookie; - for (clients = sink->clients; clients; clients = next) { + cookie = mhsink->clients_cookie; + for (clients = mhsink->clients; clients; clients = next) { GstTCPClient *client; GstMultiHandleClient *mhclient; int fd; long flags; int res; - if (cookie != sink->clients_cookie) { + if (cookie != mhsink->clients_cookie) { GST_DEBUG_OBJECT (sink, "Cookie changed finding bad fd"); goto restart; } @@ -2250,7 +2165,7 @@ gst_multi_fd_sink_handle_clients (GstMultiFdSink * sink) if (errno == EBADF) { mhclient->status = GST_CLIENT_STATUS_ERROR; /* releases the CLIENTS lock */ - gst_multi_fd_sink_remove_client_link (sink, clients); + mhsinkclass->remove_client_link (mhsink, clients); } } } @@ -2283,12 +2198,12 @@ gst_multi_fd_sink_handle_clients (GstMultiFdSink * sink) CLIENTS_LOCK (sink); restart2: - cookie = sink->clients_cookie; - for (clients = sink->clients; clients; clients = next) { + cookie = mhsink->clients_cookie; + for (clients = mhsink->clients; clients; clients = next) { GstTCPClient *client; GstMultiHandleClient *mhclient; - if (sink->clients_cookie != cookie) { + if (mhsink->clients_cookie != cookie) { GST_DEBUG_OBJECT (sink, "Restarting loop, cookie out of date"); goto restart2; } @@ -2299,32 +2214,32 @@ restart2: if (mhclient->status != GST_CLIENT_STATUS_FLUSHING && mhclient->status != GST_CLIENT_STATUS_OK) { - gst_multi_fd_sink_remove_client_link (sink, clients); + mhsinkclass->remove_client_link (mhsink, clients); continue; } if (gst_poll_fd_has_closed (sink->fdset, &client->fd)) { mhclient->status = GST_CLIENT_STATUS_CLOSED; - gst_multi_fd_sink_remove_client_link (sink, clients); + mhsinkclass->remove_client_link (mhsink, clients); continue; } if (gst_poll_fd_has_error (sink->fdset, &client->fd)) { GST_WARNING_OBJECT (sink, "gst_poll_fd_has_error for %d", client->fd.fd); mhclient->status = GST_CLIENT_STATUS_ERROR; - gst_multi_fd_sink_remove_client_link (sink, clients); + mhsinkclass->remove_client_link (mhsink, clients); continue; } if (gst_poll_fd_can_read (sink->fdset, &client->fd)) { /* handle client read */ if (!gst_multi_fd_sink_handle_client_read (sink, client)) { - gst_multi_fd_sink_remove_client_link (sink, clients); + mhsinkclass->remove_client_link (mhsink, clients); continue; } } if (gst_poll_fd_can_write (sink->fdset, &client->fd)) { /* handle client write */ if (!gst_multi_fd_sink_handle_client_write (sink, client)) { - gst_multi_fd_sink_remove_client_link (sink, clients); + mhsinkclass->remove_client_link (mhsink, clients); continue; } } @@ -2335,9 +2250,11 @@ restart2: /* we handle the client communication in another thread so that we do not block * the gstreamer thread while we select() on the client fds */ static gpointer -gst_multi_fd_sink_thread (GstMultiFdSink * sink) +gst_multi_fd_sink_thread (GstMultiHandleSink * mhsink) { - while (sink->running) { + GstMultiFdSink *sink = GST_MULTI_FD_SINK (mhsink); + + while (mhsink->running) { gst_multi_fd_sink_handle_clients (sink); } return NULL; @@ -2370,8 +2287,8 @@ gst_multi_fd_sink_render (GstBaseSink * bsink, GstBuffer * buf) goto no_caps; #endif - /* get IN_CAPS first, code below might mess with the flags */ - in_caps = GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_IN_CAPS); + /* get HEADER first, code below might mess with the flags */ + in_caps = GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_HEADER); #if 0 /* stamp the buffer with previous caps if no caps set */ @@ -2408,15 +2325,15 @@ gst_multi_fd_sink_render (GstBaseSink * bsink, GstBuffer * buf) GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)), GST_TIME_ARGS (GST_BUFFER_DURATION (buf))); - /* if we get IN_CAPS buffers, but the previous buffer was not IN_CAPS, + /* if we get HEADER buffers, but the previous buffer was not HEADER, * it means we're getting new streamheader buffers, and we should clear * the old ones */ if (in_caps && sink->previous_buffer_in_caps == FALSE) { GST_DEBUG_OBJECT (sink, - "receiving new IN_CAPS buffers, clearing old streamheader"); - g_slist_foreach (sink->streamheader, (GFunc) gst_mini_object_unref, NULL); - g_slist_free (sink->streamheader); - sink->streamheader = NULL; + "receiving new HEADER buffers, clearing old streamheader"); + g_slist_foreach (mhsink->streamheader, (GFunc) gst_mini_object_unref, NULL); + g_slist_free (mhsink->streamheader); + mhsink->streamheader = NULL; } /* save the current in_caps */ @@ -2431,9 +2348,9 @@ gst_multi_fd_sink_render (GstBaseSink * bsink, GstBuffer * buf) * We don't send the buffer to the client, since streamheaders are sent * separately when necessary. */ if (in_caps) { - GST_DEBUG_OBJECT (sink, "appending IN_CAPS buffer with length %" + GST_DEBUG_OBJECT (sink, "appending HEADER buffer with length %" G_GSIZE_FORMAT " to streamheader", gst_buffer_get_size (buf)); - sink->streamheader = g_slist_append (sink->streamheader, buf); + mhsink->streamheader = g_slist_append (mhsink->streamheader, buf); } else { /* queue the buffer, this is a regular data buffer. */ gst_multi_fd_sink_queue_buffer (sink, buf); @@ -2493,9 +2410,6 @@ gst_multi_fd_sink_set_property (GObject * object, guint prop_id, case PROP_HANDLE_READ: multifdsink->handle_read = g_value_get_boolean (value); break; - case PROP_RESEND_STREAMHEADER: - multifdsink->resend_streamheader = g_value_get_boolean (value); - break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -2521,15 +2435,6 @@ gst_multi_fd_sink_get_property (GObject * object, guint prop_id, GValue * value, case PROP_BUFFERS_SOFT_MAX: g_value_set_int (value, multifdsink->units_soft_max); break; - case PROP_BUFFERS_QUEUED: - g_value_set_uint (value, multifdsink->buffers_queued); - break; - case PROP_BYTES_QUEUED: - g_value_set_uint (value, multifdsink->bytes_queued); - break; - case PROP_TIME_QUEUED: - g_value_set_uint64 (value, multifdsink->time_queued); - break; case PROP_UNIT_TYPE: g_value_set_enum (value, multifdsink->unit_type); break; @@ -2551,9 +2456,6 @@ gst_multi_fd_sink_get_property (GObject * object, guint prop_id, GValue * value, case PROP_HANDLE_READ: g_value_set_boolean (value, multifdsink->handle_read); break; - case PROP_RESEND_STREAMHEADER: - g_value_set_boolean (value, multifdsink->resend_streamheader); - break; case PROP_NUM_FDS: g_value_set_uint (value, g_hash_table_size (multifdsink->fd_hash)); break; @@ -2564,52 +2466,21 @@ gst_multi_fd_sink_get_property (GObject * object, guint prop_id, GValue * value, } } - -/* create a socket for sending to remote machine */ static gboolean -gst_multi_fd_sink_start (GstBaseSink * bsink) +gst_multi_fd_sink_start_pre (GstMultiHandleSink * mhsink) { - GstMultiFdSinkClass *fclass; - GstMultiFdSink *this; - GstMultiHandleSink *mhsink; - - if (GST_OBJECT_FLAG_IS_SET (bsink, GST_MULTI_HANDLE_SINK_OPEN)) - return TRUE; + GstMultiFdSink *mfsink = GST_MULTI_FD_SINK (mhsink); - this = GST_MULTI_FD_SINK (bsink); - mhsink = GST_MULTI_HANDLE_SINK (bsink); - fclass = GST_MULTI_FD_SINK_GET_CLASS (this); - - GST_INFO_OBJECT (this, "starting in mode %d", this->mode); - if ((this->fdset = gst_poll_new (TRUE)) == NULL) + GST_INFO_OBJECT (mfsink, "starting in mode %d", mfsink->mode); + if ((mfsink->fdset = gst_poll_new (TRUE)) == NULL) goto socket_pair; - this->streamheader = NULL; - mhsink->bytes_to_serve = 0; - mhsink->bytes_served = 0; - - if (fclass->init) { - fclass->init (this); - } - - this->running = TRUE; - -#if !GLIB_CHECK_VERSION (2, 31, 0) - this->thread = g_thread_create ((GThreadFunc) gst_multi_fd_sink_thread, - this, TRUE, NULL); -#else - this->thread = g_thread_new ("multifdsink", - (GThreadFunc) gst_multi_fd_sink_thread, this); -#endif - - GST_OBJECT_FLAG_SET (this, GST_MULTI_HANDLE_SINK_OPEN); - return TRUE; /* ERRORS */ socket_pair: { - GST_ELEMENT_ERROR (this, RESOURCE, OPEN_READ_WRITE, (NULL), + GST_ELEMENT_ERROR (mfsink, RESOURCE, OPEN_READ_WRITE, (NULL), GST_ERROR_SYSTEM); return FALSE; } @@ -2621,113 +2492,24 @@ multifdsink_hash_remove (gpointer key, gpointer value, gpointer data) return TRUE; } -static gboolean -gst_multi_fd_sink_stop (GstBaseSink * bsink) +static void +gst_multi_fd_sink_stop_pre (GstMultiHandleSink * mhsink) { - GstMultiFdSinkClass *fclass; - GstMultiHandleSinkClass *mhclass; - GstMultiFdSink *this; - GstBuffer *buf; - int i; - - this = GST_MULTI_FD_SINK (bsink); - fclass = GST_MULTI_FD_SINK_GET_CLASS (this); - mhclass = GST_MULTI_HANDLE_SINK_GET_CLASS (this); - - if (!GST_OBJECT_FLAG_IS_SET (bsink, GST_MULTI_HANDLE_SINK_OPEN)) - return TRUE; - - this->running = FALSE; - - gst_poll_set_flushing (this->fdset, TRUE); - if (this->thread) { - GST_DEBUG_OBJECT (this, "joining thread"); - g_thread_join (this->thread); - GST_DEBUG_OBJECT (this, "joined thread"); - this->thread = NULL; - } - - /* free the clients */ - mhclass->clear (GST_MULTI_HANDLE_SINK (this)); - - if (this->streamheader) { - g_slist_foreach (this->streamheader, (GFunc) gst_mini_object_unref, NULL); - g_slist_free (this->streamheader); - this->streamheader = NULL; - } + GstMultiFdSink *mfsink = GST_MULTI_FD_SINK (mhsink); - if (fclass->close) - fclass->close (this); - - if (this->fdset) { - gst_poll_free (this->fdset); - this->fdset = NULL; - } - g_hash_table_foreach_remove (this->fd_hash, multifdsink_hash_remove, this); - - /* remove all queued buffers */ - if (this->bufqueue) { - GST_DEBUG_OBJECT (this, "Emptying bufqueue with %d buffers", - this->bufqueue->len); - for (i = this->bufqueue->len - 1; i >= 0; --i) { - buf = g_array_index (this->bufqueue, GstBuffer *, i); - GST_LOG_OBJECT (this, "Removing buffer %p (%d) with refcount %d", buf, i, - GST_MINI_OBJECT_REFCOUNT (buf)); - gst_buffer_unref (buf); - this->bufqueue = g_array_remove_index (this->bufqueue, i); - } - /* freeing the array is done in _finalize */ - } - GST_OBJECT_FLAG_UNSET (this, GST_MULTI_HANDLE_SINK_OPEN); + gst_poll_set_flushing (mfsink->fdset, TRUE); - return TRUE; } -static GstStateChangeReturn -gst_multi_fd_sink_change_state (GstElement * element, GstStateChange transition) +static void +gst_multi_fd_sink_stop_post (GstMultiHandleSink * mhsink) { - GstMultiFdSink *sink; - GstStateChangeReturn ret; - - sink = GST_MULTI_FD_SINK (element); - - /* we disallow changing the state from the streaming thread */ - if (g_thread_self () == sink->thread) - return GST_STATE_CHANGE_FAILURE; - - - switch (transition) { - case GST_STATE_CHANGE_NULL_TO_READY: - if (!gst_multi_fd_sink_start (GST_BASE_SINK (sink))) - goto start_failed; - break; - case GST_STATE_CHANGE_READY_TO_PAUSED: - break; - case GST_STATE_CHANGE_PAUSED_TO_PLAYING: - break; - default: - break; - } - - ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); - - switch (transition) { - case GST_STATE_CHANGE_PLAYING_TO_PAUSED: - break; - case GST_STATE_CHANGE_PAUSED_TO_READY: - break; - case GST_STATE_CHANGE_READY_TO_NULL: - gst_multi_fd_sink_stop (GST_BASE_SINK (sink)); - break; - default: - break; - } - return ret; + GstMultiFdSink *mfsink = GST_MULTI_FD_SINK (mhsink); - /* ERRORS */ -start_failed: - { - /* error message was posted */ - return GST_STATE_CHANGE_FAILURE; + if (mfsink->fdset) { + gst_poll_free (mfsink->fdset); + mfsink->fdset = NULL; } + g_hash_table_foreach_remove (mfsink->fd_hash, multifdsink_hash_remove, + mfsink); } diff --git a/gst/tcp/gstmultifdsink.h b/gst/tcp/gstmultifdsink.h index a224e90..a545189 100644 --- a/gst/tcp/gstmultifdsink.h +++ b/gst/tcp/gstmultifdsink.h @@ -92,26 +92,17 @@ struct _GstMultiFdSink { GstMultiHandleSink element; /*< private >*/ - GRecMutex clientslock; /* lock to protect the clients list */ - GList *clients; /* list of clients we are serving */ GHashTable *fd_hash; /* index on fd to client */ - guint clients_cookie; /* Cookie to detect changes to the clients list */ gint mode; GstPoll *fdset; - GSList *streamheader; /* GSList of GstBuffers to use as streamheader */ gboolean previous_buffer_in_caps; guint mtu; gint qos_dscp; gboolean handle_read; - GArray *bufqueue; /* global queue of buffers */ - - gboolean running; /* the thread state */ - GThread *thread; /* the sender thread */ - /* these values are used to check if a client is reading fast * enough and to control receovery */ GstTCPUnitType unit_type;/* the type of the units */ @@ -121,13 +112,6 @@ struct _GstMultiFdSink { GstTCPUnitType def_burst_unit; guint64 def_burst_value; - gboolean resend_streamheader; /* resend streamheader if it changes */ - - /* stats */ - gint buffers_queued; /* number of queued buffers */ - gint bytes_queued; /* number of queued bytes */ - gint time_queued; /* number of queued time */ - guint8 header_flags; }; @@ -145,9 +129,7 @@ struct _GstMultiFdSinkClass { GValueArray* (*get_stats) (GstMultiFdSink *sink, int fd); /* vtable */ - gboolean (*init) (GstMultiFdSink *sink); gboolean (*wait) (GstMultiFdSink *sink, GstPoll *set); - gboolean (*close) (GstMultiFdSink *sink); void (*removed) (GstMultiFdSink *sink, int fd); /* signals */ diff --git a/gst/tcp/gstmultihandlesink.c b/gst/tcp/gstmultihandlesink.c index 291039a..2dcaedf 100644 --- a/gst/tcp/gstmultihandlesink.c +++ b/gst/tcp/gstmultihandlesink.c @@ -161,7 +161,6 @@ enum #define DEFAULT_BURST_VALUE 0 #define DEFAULT_QOS_DSCP -1 -#define DEFAULT_HANDLE_READ TRUE #define DEFAULT_RESEND_STREAMHEADER TRUE @@ -170,10 +169,12 @@ enum PROP_0, #if 0 PROP_MODE, +#endif PROP_BUFFERS_QUEUED, PROP_BYTES_QUEUED, PROP_TIME_QUEUED, +#if 0 PROP_UNIT_TYPE, PROP_UNITS_MAX, PROP_UNITS_SOFT_MAX, @@ -197,11 +198,11 @@ enum PROP_BURST_VALUE, PROP_QOS_DSCP, - - PROP_HANDLE_READ, +#endif PROP_RESEND_STREAMHEADER, +#if 0 PROP_NUM_SOCKETS, #endif @@ -293,9 +294,8 @@ gst_multi_handle_sink_client_status_get_type (void) return client_status_type; } -#if 0 static void gst_multi_handle_sink_finalize (GObject * object); -#endif +static void gst_multi_handle_sink_clear (GstMultiHandleSink * mhsink); #if 0 static void gst_multi_handle_sink_remove_client_link (GstMultiHandleSink * sink, @@ -309,9 +309,9 @@ static GstFlowReturn gst_multi_handle_sink_render (GstBaseSink * bsink, GstBuffer * buf); static gboolean gst_multi_handle_sink_unlock (GstBaseSink * bsink); static gboolean gst_multi_handle_sink_unlock_stop (GstBaseSink * bsink); +#endif static GstStateChangeReturn gst_multi_handle_sink_change_state (GstElement * element, GstStateChange transition); -#endif static void gst_multi_handle_sink_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); @@ -340,9 +340,7 @@ gst_multi_handle_sink_class_init (GstMultiHandleSinkClass * klass) gobject_class->set_property = gst_multi_handle_sink_set_property; gobject_class->get_property = gst_multi_handle_sink_get_property; -#if 0 gobject_class->finalize = gst_multi_handle_sink_finalize; -#endif #if 0 g_object_class_install_property (gobject_class, PROP_BUFFERS_MAX, @@ -390,6 +388,7 @@ gst_multi_handle_sink_class_init (GstMultiHandleSinkClass * klass) "Recover client when going over this limit (-1 = no limit)", -1, G_MAXINT64, DEFAULT_UNITS_SOFT_MAX, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +#endif g_object_class_install_property (gobject_class, PROP_BUFFERS_QUEUED, g_param_spec_uint ("buffers-queued", "Buffers queued", @@ -405,7 +404,6 @@ gst_multi_handle_sink_class_init (GstMultiHandleSinkClass * klass) "Number of time currently queued", 0, G_MAXUINT64, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); #endif -#endif g_object_class_install_property (gobject_class, PROP_RECOVER_POLICY, g_param_spec_enum ("recover-policy", "Recover Policy", @@ -458,6 +456,7 @@ gst_multi_handle_sink_class_init (GstMultiHandleSinkClass * klass) g_param_spec_boolean ("handle-read", "Handle Read", "Handle client reads and discard the data", DEFAULT_HANDLE_READ, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +#endif /** * GstMultiHandleSink::resend-streamheader * @@ -471,13 +470,12 @@ gst_multi_handle_sink_class_init (GstMultiHandleSinkClass * klass) DEFAULT_RESEND_STREAMHEADER, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +#if 0 g_object_class_install_property (gobject_class, PROP_NUM_SOCKETS, g_param_spec_uint ("num-sockets", "Number of sockets", "The current number of client sockets", 0, G_MAXUINT, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); -#endif -#if 0 /** * GstMultiHandleSink::add: * @gstmultihandlesink: the multihandlesink element to emit this signal on @@ -638,9 +636,9 @@ gst_multi_handle_sink_class_init (GstMultiHandleSinkClass * klass) "Wim Taymans , " "Sebastian Dröge "); -#if 0 gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_multi_handle_sink_change_state); +#if 0 gstbasesink_class->render = GST_DEBUG_FUNCPTR (gst_multi_handle_sink_render); gstbasesink_class->unlock = GST_DEBUG_FUNCPTR (gst_multi_handle_sink_unlock); @@ -653,7 +651,9 @@ gst_multi_handle_sink_class_init (GstMultiHandleSinkClass * klass) klass->add_full = GST_DEBUG_FUNCPTR (gst_multi_handle_sink_add_full); klass->remove = GST_DEBUG_FUNCPTR (gst_multi_handle_sink_remove); klass->remove_flush = GST_DEBUG_FUNCPTR (gst_multi_handle_sink_remove_flush); +#endif klass->clear = GST_DEBUG_FUNCPTR (gst_multi_handle_sink_clear); +#if 0 klass->get_stats = GST_DEBUG_FUNCPTR (gst_multi_handle_sink_get_stats); #endif @@ -668,7 +668,9 @@ gst_multi_handle_sink_init (GstMultiHandleSink * this) CLIENTS_LOCK_INIT (this); this->clients = NULL; +#if 0 this->socket_hash = g_hash_table_new (g_direct_hash, g_int_equal); +#endif this->bufqueue = g_array_new (FALSE, TRUE, sizeof (GstBuffer *)); this->unit_type = DEFAULT_UNIT_TYPE; @@ -685,7 +687,6 @@ gst_multi_handle_sink_init (GstMultiHandleSink * this) this->def_burst_value = DEFAULT_BURST_VALUE; this->qos_dscp = DEFAULT_QOS_DSCP; - this->handle_read = DEFAULT_HANDLE_READ; this->resend_streamheader = DEFAULT_RESEND_STREAMHEADER; @@ -693,7 +694,6 @@ gst_multi_handle_sink_init (GstMultiHandleSink * this) this->cancellable = g_cancellable_new (); } -#if 0 static void gst_multi_handle_sink_finalize (GObject * object) { @@ -702,17 +702,12 @@ gst_multi_handle_sink_finalize (GObject * object) this = GST_MULTI_HANDLE_SINK (object); CLIENTS_LOCK_CLEAR (this); - g_hash_table_destroy (this->socket_hash); g_array_free (this->bufqueue, TRUE); - if (this->cancellable) { - g_object_unref (this->cancellable); - this->cancellable = NULL; - } - G_OBJECT_CLASS (parent_class)->finalize (object); } +#if 0 static gint setup_dscp_client (GstMultiHandleSink * sink, GstSocketClient * client) { @@ -985,36 +980,46 @@ gst_multi_handle_sink_remove_flush (GstMultiHandleSink * sink, GSocket * socket) done: CLIENTS_UNLOCK (sink); } +#endif /* can be called both through the signal (i.e. from any thread) or when * stopping, after the writing thread has shut down */ -void -gst_multi_handle_sink_clear (GstMultiHandleSink * sink) +static void +gst_multi_handle_sink_clear (GstMultiHandleSink * mhsink) { - GList *clients; + GList *clients, *next; guint32 cookie; + GstMultiHandleSinkClass *mhsinkclass = + GST_MULTI_HANDLE_SINK_GET_CLASS (mhsink); - GST_DEBUG_OBJECT (sink, "clearing all clients"); + GST_DEBUG_OBJECT (mhsink, "clearing all clients"); - CLIENTS_LOCK (sink); + CLIENTS_LOCK (mhsink); restart: - cookie = sink->clients_cookie; - for (clients = sink->clients; clients; clients = clients->next) { - GstSocketClient *client; + cookie = mhsink->clients_cookie; + for (clients = mhsink->clients; clients; clients = next) { + GstMultiHandleClient *mhclient; - if (cookie != sink->clients_cookie) { - GST_DEBUG_OBJECT (sink, "cookie changed while removing all clients"); + if (cookie != mhsink->clients_cookie) { + GST_DEBUG_OBJECT (mhsink, "cookie changed while removing all clients"); goto restart; } - client = clients->data; - client->status = GST_CLIENT_STATUS_REMOVED; - gst_multi_handle_sink_remove_client_link (sink, clients); + mhclient = (GstMultiHandleClient *) clients->data; + next = g_list_next (clients); + + mhclient->status = GST_CLIENT_STATUS_REMOVED; + /* the next call changes the list, which is why we iterate + * with a temporary next pointer */ + mhsinkclass->remove_client_link (mhsink, clients); } + if (mhsinkclass->clear_post) + mhsinkclass->clear_post (mhsink); - CLIENTS_UNLOCK (sink); + CLIENTS_UNLOCK (mhsink); } +#if 0 /* "get-stats" signal implementation */ GstStructure * @@ -1363,13 +1368,13 @@ gst_multi_handle_sink_client_queue_buffer (GstMultiHandleSink * sink, } #endif -#if 0 -static gboolean +// FIXME: privatize again ? +gboolean is_sync_frame (GstMultiHandleSink * sink, GstBuffer * buffer) { if (GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT)) { return FALSE; - } else if (!GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_IN_CAPS)) { + } else if (!GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_HEADER)) { return TRUE; } @@ -1381,7 +1386,7 @@ is_sync_frame (GstMultiHandleSink * sink, GstBuffer * buffer) * 1 will search forwards. * Returns: the index or -1 if there is no keyframe after idx. */ -static gint +gint find_syncframe (GstMultiHandleSink * sink, gint idx, gint direction) { gint i, len, result; @@ -1406,10 +1411,7 @@ find_syncframe (GstMultiHandleSink * sink, gint idx, gint direction) } return result; } -#endif -#define find_next_syncframe(s,i) find_syncframe(s,i,1) -#define find_prev_syncframe(s,i) find_syncframe(s,i,-1) #if 0 /* Get the number of buffers from the buffer queue needed to satisfy @@ -2573,9 +2575,6 @@ gst_multi_handle_sink_set_property (GObject * object, guint prop_id, multihandlesink->qos_dscp = g_value_get_int (value); setup_dscp (multihandlesink); break; - case PROP_HANDLE_READ: - multihandlesink->handle_read = g_value_get_boolean (value); - break; case PROP_RESEND_STREAMHEADER: multihandlesink->resend_streamheader = g_value_get_boolean (value); break; @@ -2612,7 +2611,6 @@ gst_multi_handle_sink_get_property (GObject * object, guint prop_id, case PROP_BUFFERS_MIN: g_value_set_int (value, multihandlesink->buffers_min); break; -#if 0 case PROP_BUFFERS_QUEUED: g_value_set_uint (value, multihandlesink->buffers_queued); break; @@ -2622,6 +2620,7 @@ gst_multi_handle_sink_get_property (GObject * object, guint prop_id, case PROP_TIME_QUEUED: g_value_set_uint64 (value, multihandlesink->time_queued); break; +#if 0 case PROP_UNIT_TYPE: g_value_set_enum (value, multihandlesink->unit_type); break; @@ -2657,9 +2656,6 @@ gst_multi_handle_sink_get_property (GObject * object, guint prop_id, case PROP_QOS_DSCP: g_value_set_int (value, multihandlesink->qos_dscp); break; - case PROP_HANDLE_READ: - g_value_set_boolean (value, multihandlesink->handle_read); - break; case PROP_RESEND_STREAMHEADER: g_value_set_boolean (value, multihandlesink->resend_streamheader); break; @@ -2674,6 +2670,39 @@ gst_multi_handle_sink_get_property (GObject * object, guint prop_id, } } +/* create a socket for sending to remote machine */ +gboolean +gst_multi_handle_sink_start (GstBaseSink * bsink) +{ + GstMultiHandleSinkClass *mhsclass; + GstMultiHandleSink *mhsink; + + if (GST_OBJECT_FLAG_IS_SET (bsink, GST_MULTI_HANDLE_SINK_OPEN)) + return TRUE; + + mhsink = GST_MULTI_HANDLE_SINK (bsink); + mhsclass = GST_MULTI_HANDLE_SINK_GET_CLASS (mhsink); + + if (!mhsclass->start_pre (mhsink)) + return FALSE; + + mhsink->streamheader = NULL; + mhsink->bytes_to_serve = 0; + mhsink->bytes_served = 0; + + if (mhsclass->init) { + mhsclass->init (mhsink); + } + + mhsink->running = TRUE; + + mhsink->thread = g_thread_new ("multihandlesink", + (GThreadFunc) mhsclass->thread, mhsink); + + GST_OBJECT_FLAG_SET (bsink, GST_MULTI_HANDLE_SINK_OPEN); + + return TRUE; +} #if 0 /* create a socket for sending to remote machine */ @@ -2736,6 +2765,64 @@ multisocketsink_hash_remove (gpointer key, gpointer value, gpointer data) } #endif +// FIXME: privatize again +gboolean +gst_multi_handle_sink_stop (GstBaseSink * bsink) +{ + GstMultiHandleSinkClass *mhclass; + GstBuffer *buf; + gint i; + GstMultiHandleSink *mhsink = GST_MULTI_HANDLE_SINK (bsink); + + mhclass = GST_MULTI_HANDLE_SINK_GET_CLASS (mhsink); + + if (!GST_OBJECT_FLAG_IS_SET (bsink, GST_MULTI_HANDLE_SINK_OPEN)) + return TRUE; + + mhsink->running = FALSE; + + mhclass->stop_pre (mhsink); + + if (mhsink->thread) { + GST_DEBUG_OBJECT (mhsink, "joining thread"); + g_thread_join (mhsink->thread); + GST_DEBUG_OBJECT (mhsink, "joined thread"); + mhsink->thread = NULL; + } + + /* free the clients */ + mhclass->clear (GST_MULTI_HANDLE_SINK (mhsink)); + + if (mhsink->streamheader) { + g_slist_foreach (mhsink->streamheader, (GFunc) gst_mini_object_unref, NULL); + g_slist_free (mhsink->streamheader); + mhsink->streamheader = NULL; + } + + if (mhclass->close) + mhclass->close (mhsink); + + mhclass->stop_post (mhsink); + + /* remove all queued buffers */ + if (mhsink->bufqueue) { + GST_DEBUG_OBJECT (mhsink, "Emptying bufqueue with %d buffers", + mhsink->bufqueue->len); + for (i = mhsink->bufqueue->len - 1; i >= 0; --i) { + buf = g_array_index (mhsink->bufqueue, GstBuffer *, i); + GST_LOG_OBJECT (mhsink, "Removing buffer %p (%d) with refcount %d", buf, + i, GST_MINI_OBJECT_REFCOUNT (buf)); + gst_buffer_unref (buf); + mhsink->bufqueue = g_array_remove_index (mhsink->bufqueue, i); + } + /* freeing the array is done in _finalize */ + } + GST_OBJECT_FLAG_UNSET (mhsink, GST_MULTI_HANDLE_SINK_OPEN); + + return TRUE; +} + + #if 0 static gboolean gst_multi_handle_sink_stop (GstBaseSink * bsink) @@ -2802,7 +2889,6 @@ gst_multi_handle_sink_stop (GstBaseSink * bsink) } #endif -#if 0 static GstStateChangeReturn gst_multi_handle_sink_change_state (GstElement * element, GstStateChange transition) @@ -2853,6 +2939,7 @@ start_failed: } } +#if 0 static gboolean gst_multi_handle_sink_unlock (GstBaseSink * bsink) { diff --git a/gst/tcp/gstmultihandlesink.h b/gst/tcp/gstmultihandlesink.h index efbff1e..87aafc5 100644 --- a/gst/tcp/gstmultihandlesink.h +++ b/gst/tcp/gstmultihandlesink.h @@ -123,6 +123,8 @@ typedef enum /* structure for a client */ typedef struct { + gchar debug[30]; /* a debug string used in debug calls to + identify the client */ gint bufpos; /* position of this client in the global queue */ gint flushcount; /* the remaining number of buffers to flush out or -1 if the client is not flushing. */ @@ -161,10 +163,20 @@ typedef struct { guint64 last_buffer_ts; } GstMultiHandleClient; -#define CLIENTS_LOCK_INIT(socketsink) (g_rec_mutex_init(&socketsink->clientslock)) -#define CLIENTS_LOCK_CLEAR(socketsink) (g_rec_mutex_clear(&socketsink->clientslock)) -#define CLIENTS_LOCK(socketsink) (g_rec_mutex_lock(&socketsink->clientslock)) -#define CLIENTS_UNLOCK(socketsink) (g_rec_mutex_unlock(&socketsink->clientslock)) +// FIXME: remove cast ? +#define CLIENTS_LOCK_INIT(mhsink) (g_rec_mutex_init(&(GST_MULTI_HANDLE_SINK(mhsink))->clientslock)) +#define CLIENTS_LOCK_CLEAR(mhsink) (g_rec_mutex_clear(&(GST_MULTI_HANDLE_SINK(mhsink))->clientslock)) +#define CLIENTS_LOCK(mhsink) (g_rec_mutex_lock(&(GST_MULTI_HANDLE_SINK(mhsink))->clientslock)) +#define CLIENTS_UNLOCK(mhsink) (g_rec_mutex_unlock(&(GST_MULTI_HANDLE_SINK(mhsink))->clientslock)) + +// FIXME: internalize in .c file ? +gint +find_syncframe (GstMultiHandleSink * sink, gint idx, gint direction); +#define find_next_syncframe(s,i) find_syncframe(s,i,1) +#define find_prev_syncframe(s,i) find_syncframe(s,i,-1) +gboolean is_sync_frame (GstMultiHandleSink * sink, GstBuffer * buffer); +gboolean gst_multi_handle_sink_stop (GstBaseSink * bsink); +gboolean gst_multi_handle_sink_start (GstBaseSink * bsink); /** * GstMultiHandleSink: @@ -180,7 +192,6 @@ struct _GstMultiHandleSink { GRecMutex clientslock; /* lock to protect the clients list */ GList *clients; /* list of clients we are serving */ - GHashTable *socket_hash; /* index on socket to client */ guint clients_cookie; /* Cookie to detect changes to the clients list */ GMainContext *main_context; @@ -191,7 +202,6 @@ struct _GstMultiHandleSink { guint mtu; gint qos_dscp; - gboolean handle_read; GArray *bufqueue; /* global queue of buffers */ @@ -238,7 +248,14 @@ struct _GstMultiHandleSinkClass { void (*remove) (GstMultiHandleSink *sink, GSocket *socket); void (*remove_flush) (GstMultiHandleSink *sink, GSocket *socket); void (*clear) (GstMultiHandleSink *sink); + void (*clear_post) (GstMultiHandleSink *sink); + void (*stop_pre) (GstMultiHandleSink *sink); + void (*stop_post) (GstMultiHandleSink *sink); + gboolean (*start_pre) (GstMultiHandleSink *sink); + gpointer (*thread) (GstMultiHandleSink *sink); + GstStructure* (*get_stats) (GstMultiHandleSink *sink, GSocket *socket); + void (*remove_client_link) (GstMultiHandleSink * sink, GList * link); /* vtable */ gboolean (*init) (GstMultiHandleSink *sink); diff --git a/gst/tcp/gstmultioutputsink.c b/gst/tcp/gstmultioutputsink.c new file mode 100644 index 0000000..037b352 --- /dev/null +++ b/gst/tcp/gstmultioutputsink.c @@ -0,0 +1,2803 @@ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen + * Copyright (C) <2004> Thomas Vander Stichele + * Copyright (C) 2006 Wim Taymans + * Copyright (C) <2011> Collabora Ltd. + * Author: Sebastian Dröge + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:element-multioutputsink + * @see_also: tcpserversink + * + * This plugin writes incoming data to a set of file descriptors. The + * file descriptors can be added to multioutputsink by emitting the #GstMultiOutputSink::add signal. + * For each descriptor added, the #GstMultiOutputSink::client-added signal will be called. + * + * As of version 0.10.8, a client can also be added with the #GstMultiOutputSink::add-full signal + * that allows for more control over what and how much data a client + * initially receives. + * + * Clients can be removed from multioutputsink by emitting the #GstMultiOutputSink::remove signal. For + * each descriptor removed, the #GstMultiOutputSink::client-removed signal will be called. The + * #GstMultiOutputSink::client-removed signal can also be fired when multioutputsink decides that a + * client is not active anymore or, depending on the value of the + * #GstMultiOutputSink:recover-policy property, if the client is reading too slowly. + * In all cases, multioutputsink will never close a file descriptor itself. + * The user of multioutputsink is responsible for closing all file descriptors. + * This can for example be done in response to the #GstMultiOutputSink::client-fd-removed signal. + * Note that multioutputsink still has a reference to the file descriptor when the + * #GstMultiOutputSink::client-removed signal is emitted, so that "get-stats" can be performed on + * the descriptor; it is therefore not safe to close the file descriptor in + * the #GstMultiOutputSink::client-removed signal handler, and you should use the + * #GstMultiOutputSink::client-fd-removed signal to safely close the fd. + * + * Multioutputsink internally keeps a queue of the incoming buffers and uses a + * separate thread to send the buffers to the clients. This ensures that no + * client write can block the pipeline and that clients can read with different + * speeds. + * + * When adding a client to multioutputsink, the #GstMultiOutputSink:sync-method property will define + * which buffer in the queued buffers will be sent first to the client. Clients + * can be sent the most recent buffer (which might not be decodable by the + * client if it is not a keyframe), the next keyframe received in + * multioutputsink (which can take some time depending on the keyframe rate), or the + * last received keyframe (which will cause a simple burst-on-connect). + * Multioutputsink will always keep at least one keyframe in its internal buffers + * when the sync-mode is set to latest-keyframe. + * + * As of version 0.10.8, there are additional values for the #GstMultiOutputSink:sync-method + * property to allow finer control over burst-on-connect behaviour. By selecting + * the 'burst' method a minimum burst size can be chosen, 'burst-keyframe' + * additionally requires that the burst begin with a keyframe, and + * 'burst-with-keyframe' attempts to burst beginning with a keyframe, but will + * prefer a minimum burst size even if it requires not starting with a keyframe. + * + * Multioutputsink can be instructed to keep at least a minimum amount of data + * expressed in time or byte units in its internal queues with the + * #GstMultiOutputSink:time-min and #GstMultiOutputSink:bytes-min properties respectively. + * These properties are useful if the application adds clients with the + * #GstMultiOutputSink::add-full signal to make sure that a burst connect can + * actually be honored. + * + * When streaming data, clients are allowed to read at a different rate than + * the rate at which multioutputsink receives data. If the client is reading too + * fast, no data will be send to the client until multioutputsink receives more + * data. If the client, however, reads too slowly, data for that client will be + * queued up in multioutputsink. Two properties control the amount of data + * (buffers) that is queued in multioutputsink: #GstMultiOutputSink:buffers-max and + * #GstMultiOutputSink:buffers-soft-max. A client that falls behind by + * #GstMultiOutputSink:buffers-max is removed from multioutputsink forcibly. + * + * A client with a lag of at least #GstMultiOutputSink:buffers-soft-max enters the recovery + * procedure which is controlled with the #GstMultiOutputSink:recover-policy property. + * A recover policy of NONE will do nothing, RESYNC_LATEST will send the most recently + * received buffer as the next buffer for the client, RESYNC_SOFT_LIMIT + * positions the client to the soft limit in the buffer queue and + * RESYNC_KEYFRAME positions the client at the most recent keyframe in the + * buffer queue. + * + * multioutputsink will by default synchronize on the clock before serving the + * buffers to the clients. This behaviour can be disabled by setting the sync + * property to FALSE. Multioutputsink will by default not do QoS and will never + * drop late buffers. + * + * Last reviewed on 2006-09-12 (0.10.10) + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "gstmultioutputsink.h" +#include "gsttcp-marshal.h" + +#ifndef G_OS_WIN32 +#include +#endif + +#define NOT_IMPLEMENTED 0 + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +GST_DEBUG_CATEGORY_STATIC (multioutputsink_debug); +#define GST_CAT_DEFAULT (multioutputsink_debug) + +/* MultiOutputSink signals and args */ +enum +{ + /* methods */ + SIGNAL_ADD, + SIGNAL_ADD_BURST, + SIGNAL_REMOVE, + SIGNAL_REMOVE_FLUSH, + SIGNAL_CLEAR, + SIGNAL_GET_STATS, + + /* signals */ + SIGNAL_CLIENT_ADDED, + SIGNAL_CLIENT_REMOVED, + SIGNAL_CLIENT_OUTPUT_REMOVED, + + LAST_SIGNAL +}; + + +/* this is really arbitrarily chosen */ +#define DEFAULT_MODE 1 +#define DEFAULT_BUFFERS_MAX -1 +#define DEFAULT_BUFFERS_SOFT_MAX -1 +#define DEFAULT_TIME_MIN -1 +#define DEFAULT_BYTES_MIN -1 +#define DEFAULT_BUFFERS_MIN -1 +#define DEFAULT_UNIT_TYPE GST_FORMAT_BUFFERS +#define DEFAULT_UNITS_MAX -1 +#define DEFAULT_UNITS_SOFT_MAX -1 +#define DEFAULT_RECOVER_POLICY GST_RECOVER_POLICY_NONE +#define DEFAULT_TIMEOUT 0 +#define DEFAULT_SYNC_METHOD GST_SYNC_METHOD_LATEST + +#define DEFAULT_BURST_FORMAT GST_FORMAT_UNDEFINED +#define DEFAULT_BURST_VALUE 0 + +#define DEFAULT_QOS_DSCP -1 +#define DEFAULT_HANDLE_READ TRUE + +#define DEFAULT_RESEND_STREAMHEADER TRUE + +enum +{ + PROP_0, + PROP_MODE, + PROP_BUFFERS_QUEUED, + PROP_BYTES_QUEUED, + PROP_TIME_QUEUED, + + PROP_UNIT_TYPE, + PROP_UNITS_MAX, + PROP_UNITS_SOFT_MAX, + + PROP_BUFFERS_MAX, + PROP_BUFFERS_SOFT_MAX, + + PROP_TIME_MIN, + PROP_BYTES_MIN, + PROP_BUFFERS_MIN, + + PROP_RECOVER_POLICY, + PROP_TIMEOUT, + PROP_SYNC_METHOD, + PROP_BYTES_TO_SERVE, + PROP_BYTES_SERVED, + + PROP_BURST_FORMAT, + PROP_BURST_VALUE, + + PROP_QOS_DSCP, + + PROP_HANDLE_READ, + + PROP_RESEND_STREAMHEADER, + + PROP_NUM_OUTPUTS, + + PROP_LAST +}; + +#define GST_TYPE_RECOVER_POLICY (gst_multi_output_sink_recover_policy_get_type()) +static GType +gst_multi_output_sink_recover_policy_get_type (void) +{ + static GType recover_policy_type = 0; + static const GEnumValue recover_policy[] = { + {GST_RECOVER_POLICY_NONE, + "Do not try to recover", "none"}, + {GST_RECOVER_POLICY_RESYNC_LATEST, + "Resync client to latest buffer", "latest"}, + {GST_RECOVER_POLICY_RESYNC_SOFT_LIMIT, + "Resync client to soft limit", "soft-limit"}, + {GST_RECOVER_POLICY_RESYNC_KEYFRAME, + "Resync client to most recent keyframe", "keyframe"}, + {0, NULL, NULL}, + }; + + if (!recover_policy_type) { + recover_policy_type = + g_enum_register_static ("GstMultiOutputSinkRecoverPolicy", + recover_policy); + } + return recover_policy_type; +} + +#define GST_TYPE_SYNC_METHOD (gst_multi_output_sink_sync_method_get_type()) +static GType +gst_multi_output_sink_sync_method_get_type (void) +{ + static GType sync_method_type = 0; + static const GEnumValue sync_method[] = { + {GST_SYNC_METHOD_LATEST, + "Serve starting from the latest buffer", "latest"}, + {GST_SYNC_METHOD_NEXT_KEYFRAME, + "Serve starting from the next keyframe", "next-keyframe"}, + {GST_SYNC_METHOD_LATEST_KEYFRAME, + "Serve everything since the latest keyframe (burst)", + "latest-keyframe"}, + {GST_SYNC_METHOD_BURST, "Serve burst-value data to client", "burst"}, + {GST_SYNC_METHOD_BURST_KEYFRAME, + "Serve burst-value data starting on a keyframe", + "burst-keyframe"}, + {GST_SYNC_METHOD_BURST_WITH_KEYFRAME, + "Serve burst-value data preferably starting on a keyframe", + "burst-with-keyframe"}, + {0, NULL, NULL}, + }; + + if (!sync_method_type) { + sync_method_type = + g_enum_register_static ("GstMultiOutputSinkSyncMethod", sync_method); + } + return sync_method_type; +} + +#define GST_TYPE_CLIENT_STATUS (gst_multi_output_sink_client_status_get_type()) +static GType +gst_multi_output_sink_client_status_get_type (void) +{ + static GType client_status_type = 0; + static const GEnumValue client_status[] = { + {GST_CLIENT_STATUS_OK, "ok", "ok"}, + {GST_CLIENT_STATUS_CLOSED, "Closed", "closed"}, + {GST_CLIENT_STATUS_REMOVED, "Removed", "removed"}, + {GST_CLIENT_STATUS_SLOW, "Too slow", "slow"}, + {GST_CLIENT_STATUS_ERROR, "Error", "error"}, + {GST_CLIENT_STATUS_DUPLICATE, "Duplicate", "duplicate"}, + {GST_CLIENT_STATUS_FLUSHING, "Flushing", "flushing"}, + {0, NULL, NULL}, + }; + + if (!client_status_type) { + client_status_type = + g_enum_register_static ("GstMultiOutputSinkClientStatus", + client_status); + } + return client_status_type; +} + +static void gst_multi_output_sink_finalize (GObject * object); + +static void gst_multi_output_sink_remove_client_link (GstMultiOutputSink * sink, + GList * link); +static gboolean gst_multi_output_sink_output_condition (GstOutput * output, + GIOCondition condition, GstMultiOutputSink * sink); + +static GstFlowReturn gst_multi_output_sink_render (GstBaseSink * bsink, + GstBuffer * buf); +#if 0 +static gboolean gst_multi_output_sink_unlock (GstBaseSink * bsink); +static gboolean gst_multi_output_sink_unlock_stop (GstBaseSink * bsink); +#endif +static GstStateChangeReturn gst_multi_output_sink_change_state (GstElement * + element, GstStateChange transition); + +static void gst_multi_output_sink_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_multi_output_sink_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +#define gst_multi_output_sink_parent_class parent_class +G_DEFINE_TYPE (GstMultiOutputSink, gst_multi_output_sink, GST_TYPE_BASE_SINK); + +static guint gst_multi_output_sink_signals[LAST_SIGNAL] = { 0 }; + +static void +gst_multi_output_sink_class_init (GstMultiOutputSinkClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + GstBaseSinkClass *gstbasesink_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + gstbasesink_class = (GstBaseSinkClass *) klass; + + gobject_class->set_property = gst_multi_output_sink_set_property; + gobject_class->get_property = gst_multi_output_sink_get_property; + gobject_class->finalize = gst_multi_output_sink_finalize; + + g_object_class_install_property (gobject_class, PROP_BUFFERS_MAX, + g_param_spec_int ("buffers-max", "Buffers max", + "max number of buffers to queue for a client (-1 = no limit)", -1, + G_MAXINT, DEFAULT_BUFFERS_MAX, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_BUFFERS_SOFT_MAX, + g_param_spec_int ("buffers-soft-max", "Buffers soft max", + "Recover client when going over this limit (-1 = no limit)", -1, + G_MAXINT, DEFAULT_BUFFERS_SOFT_MAX, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_BYTES_MIN, + g_param_spec_int ("bytes-min", "Bytes min", + "min number of bytes to queue (-1 = as little as possible)", -1, + G_MAXINT, DEFAULT_BYTES_MIN, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_TIME_MIN, + g_param_spec_int64 ("time-min", "Time min", + "min number of time to queue (-1 = as little as possible)", -1, + G_MAXINT64, DEFAULT_TIME_MIN, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_BUFFERS_MIN, + g_param_spec_int ("buffers-min", "Buffers min", + "min number of buffers to queue (-1 = as few as possible)", -1, + G_MAXINT, DEFAULT_BUFFERS_MIN, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_UNIT_TYPE, + g_param_spec_enum ("unit-type", "Units type", + "The unit to measure the max/soft-max/queued properties", + GST_TYPE_FORMAT, DEFAULT_UNIT_TYPE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_UNITS_MAX, + g_param_spec_int64 ("units-max", "Units max", + "max number of units to queue (-1 = no limit)", -1, G_MAXINT64, + DEFAULT_UNITS_MAX, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_UNITS_SOFT_MAX, + g_param_spec_int64 ("units-soft-max", "Units soft max", + "Recover client when going over this limit (-1 = no limit)", -1, + G_MAXINT64, DEFAULT_UNITS_SOFT_MAX, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_BUFFERS_QUEUED, + g_param_spec_uint ("buffers-queued", "Buffers queued", + "Number of buffers currently queued", 0, G_MAXUINT, 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); +#if NOT_IMPLEMENTED + g_object_class_install_property (gobject_class, PROP_BYTES_QUEUED, + g_param_spec_uint ("bytes-queued", "Bytes queued", + "Number of bytes currently queued", 0, G_MAXUINT, 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_TIME_QUEUED, + g_param_spec_uint64 ("time-queued", "Time queued", + "Number of time currently queued", 0, G_MAXUINT64, 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); +#endif + + g_object_class_install_property (gobject_class, PROP_RECOVER_POLICY, + g_param_spec_enum ("recover-policy", "Recover Policy", + "How to recover when client reaches the soft max", + GST_TYPE_RECOVER_POLICY, DEFAULT_RECOVER_POLICY, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_TIMEOUT, + g_param_spec_uint64 ("timeout", "Timeout", + "Maximum inactivity timeout in nanoseconds for a client (0 = no limit)", + 0, G_MAXUINT64, DEFAULT_TIMEOUT, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_SYNC_METHOD, + g_param_spec_enum ("sync-method", "Sync Method", + "How to sync new clients to the stream", GST_TYPE_SYNC_METHOD, + DEFAULT_SYNC_METHOD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_BYTES_TO_SERVE, + g_param_spec_uint64 ("bytes-to-serve", "Bytes to serve", + "Number of bytes received to serve to clients", 0, G_MAXUINT64, 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_BYTES_SERVED, + g_param_spec_uint64 ("bytes-served", "Bytes served", + "Total number of bytes send to all clients", 0, G_MAXUINT64, 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_BURST_FORMAT, + g_param_spec_enum ("burst-format", "Burst format", + "The format of the burst units (when sync-method is burst[[-with]-keyframe])", + GST_TYPE_FORMAT, DEFAULT_BURST_FORMAT, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_BURST_VALUE, + g_param_spec_uint64 ("burst-value", "Burst value", + "The amount of burst expressed in burst-unit", 0, G_MAXUINT64, + DEFAULT_BURST_VALUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_QOS_DSCP, + g_param_spec_int ("qos-dscp", "QoS diff srv code point", + "Quality of Service, differentiated services code point (-1 default)", + -1, 63, DEFAULT_QOS_DSCP, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstMultiOutputSink::handle-read + * + * Handle read requests from clients and discard the data. + * + * Since: 0.10.23 + */ + g_object_class_install_property (gobject_class, PROP_HANDLE_READ, + g_param_spec_boolean ("handle-read", "Handle Read", + "Handle client reads and discard the data", + DEFAULT_HANDLE_READ, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** + * GstMultiOutputSink::resend-streamheader + * + * Resend the streamheaders to existing clients when they change. + * + * Since: 0.10.23 + */ + g_object_class_install_property (gobject_class, PROP_RESEND_STREAMHEADER, + g_param_spec_boolean ("resend-streamheader", "Resend streamheader", + "Resend the streamheader if it changes in the caps", + DEFAULT_RESEND_STREAMHEADER, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_NUM_OUTPUTS, + g_param_spec_uint ("num-outputs", "Number of outputs", + "The current number of client outputs", + 0, G_MAXUINT, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * GstMultiOutputSink::add: + * @gstmultioutputsink: the multioutputsink element to emit this signal on + * @output: the output to add to multioutputsink + * + * Hand the given open output to multioutputsink to write to. + */ + gst_multi_output_sink_signals[SIGNAL_ADD] = + g_signal_new ("add", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GstMultiOutputSinkClass, add), NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, G_TYPE_OUTPUT); + /** + * GstMultiOutputSink::add-full: + * @gstmultioutputsink: the multioutputsink element to emit this signal on + * @output: the output to add to multioutputsink + * @sync: the sync method to use + * @format_min: the format of @value_min + * @value_min: the minimum amount of data to burst expressed in + * @format_min units. + * @format_max: the format of @value_max + * @value_max: the maximum amount of data to burst expressed in + * @format_max units. + * + * Hand the given open output to multioutputsink to write to and + * specify the burst parameters for the new connection. + */ + gst_multi_output_sink_signals[SIGNAL_ADD_BURST] = + g_signal_new ("add-full", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GstMultiOutputSinkClass, add_full), NULL, NULL, + gst_tcp_marshal_VOID__OBJECT_ENUM_ENUM_UINT64_ENUM_UINT64, G_TYPE_NONE, 6, + G_TYPE_OUTPUT, GST_TYPE_SYNC_METHOD, GST_TYPE_FORMAT, G_TYPE_UINT64, + GST_TYPE_FORMAT, G_TYPE_UINT64); + /** + * GstMultiOutputSink::remove: + * @gstmultioutputsink: the multioutputsink element to emit this signal on + * @output: the output to remove from multioutputsink + * + * Remove the given open output from multioutputsink. + */ + gst_multi_output_sink_signals[SIGNAL_REMOVE] = + g_signal_new ("remove", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GstMultiOutputSinkClass, remove), NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, G_TYPE_OUTPUT); + /** + * GstMultiOutputSink::remove-flush: + * @gstmultioutputsink: the multioutputsink element to emit this signal on + * @output: the output to remove from multioutputsink + * + * Remove the given open output from multioutputsink after flushing all + * the pending data to the output. + */ + gst_multi_output_sink_signals[SIGNAL_REMOVE_FLUSH] = + g_signal_new ("remove-flush", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GstMultiOutputSinkClass, remove_flush), NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, G_TYPE_OUTPUT); + /** + * GstMultiOutputSink::clear: + * @gstmultioutputsink: the multioutputsink element to emit this signal on + * + * Remove all outputs from multioutputsink. Since multioutputsink did not + * open outputs itself, it does not explicitly close the outputs. The application + * should do so by connecting to the client-output-removed callback. + */ + gst_multi_output_sink_signals[SIGNAL_CLEAR] = + g_signal_new ("clear", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GstMultiOutputSinkClass, clear), NULL, NULL, + g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + + /** + * GstMultiOutputSink::get-stats: + * @gstmultioutputsink: the multioutputsink element to emit this signal on + * @output: the output to get stats of from multioutputsink + * + * Get statistics about @output. This function returns a GstStructure. + * + * Returns: a GstStructure with the statistics. The structure contains + * values that represent: total number of bytes sent, time + * when the client was added, time when the client was + * disconnected/removed, time the client is/was active, last activity + * time (in epoch seconds), number of buffers dropped. + * All times are expressed in nanoseconds (GstClockTime). + */ + gst_multi_output_sink_signals[SIGNAL_GET_STATS] = + g_signal_new ("get-stats", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GstMultiOutputSinkClass, get_stats), NULL, NULL, + gst_tcp_marshal_BOXED__OBJECT, GST_TYPE_STRUCTURE, 1, G_TYPE_OUTPUT); + + /** + * GstMultiOutputSink::client-added: + * @gstmultioutputsink: the multioutputsink element that emitted this signal + * @output: the output that was added to multioutputsink + * + * The given output was added to multioutputsink. This signal will + * be emitted from the streaming thread so application should be prepared + * for that. + */ + gst_multi_output_sink_signals[SIGNAL_CLIENT_ADDED] = + g_signal_new ("client-added", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstMultiOutputSinkClass, + client_added), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, G_TYPE_OBJECT); + /** + * GstMultiOutputSink::client-removed: + * @gstmultioutputsink: the multioutputsink element that emitted this signal + * @output: the output that is to be removed from multioutputsink + * @status: the reason why the client was removed + * + * The given output is about to be removed from multioutputsink. This + * signal will be emitted from the streaming thread so applications should + * be prepared for that. + * + * @gstmultioutputsink still holds a handle to @output so it is possible to call + * the get-stats signal from this callback. For the same reason it is + * not safe to close() and reuse @output in this callback. + */ + gst_multi_output_sink_signals[SIGNAL_CLIENT_REMOVED] = + g_signal_new ("client-removed", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstMultiOutputSinkClass, + client_removed), NULL, NULL, gst_tcp_marshal_VOID__OBJECT_ENUM, + G_TYPE_NONE, 2, G_TYPE_INT, GST_TYPE_CLIENT_STATUS); + /** + * GstMultiOutputSink::client-output-removed: + * @gstmultioutputsink: the multioutputsink element that emitted this signal + * @output: the output that was removed from multioutputsink + * + * The given output was removed from multioutputsink. This signal will + * be emitted from the streaming thread so applications should be prepared + * for that. + * + * In this callback, @gstmultioutputsink has removed all the information + * associated with @output and it is therefore not possible to call get-stats + * with @output. It is however safe to close() and reuse @fd in the callback. + * + * Since: 0.10.7 + */ + gst_multi_output_sink_signals[SIGNAL_CLIENT_OUTPUT_REMOVED] = + g_signal_new ("client-output-removed", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstMultiOutputSinkClass, + client_output_removed), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, G_TYPE_OUTPUT); + + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&sinktemplate)); + + gst_element_class_set_details_simple (gstelement_class, + "Multi output sink", "Sink/Network", + "Send data to multiple outputs", + "Thomas Vander Stichele , " + "Wim Taymans , " + "Sebastian Dröge "); + + gstelement_class->change_state = + GST_DEBUG_FUNCPTR (gst_multi_output_sink_change_state); + + gstbasesink_class->render = GST_DEBUG_FUNCPTR (gst_multi_output_sink_render); +#if 0 + gstbasesink_class->unlock = GST_DEBUG_FUNCPTR (gst_multi_output_sink_unlock); + gstbasesink_class->unlock_stop = + GST_DEBUG_FUNCPTR (gst_multi_output_sink_unlock_stop); +#endif + klass->add = GST_DEBUG_FUNCPTR (gst_multi_output_sink_add); + klass->add_full = GST_DEBUG_FUNCPTR (gst_multi_output_sink_add_full); + klass->remove = GST_DEBUG_FUNCPTR (gst_multi_output_sink_remove); + klass->remove_flush = GST_DEBUG_FUNCPTR (gst_multi_output_sink_remove_flush); + klass->clear = GST_DEBUG_FUNCPTR (gst_multi_output_sink_clear); + klass->get_stats = GST_DEBUG_FUNCPTR (gst_multi_output_sink_get_stats); + + GST_DEBUG_CATEGORY_INIT (multioutputsink_debug, "multioutputsink", 0, + "Multi output sink"); +} + +static void +gst_multi_output_sink_init (GstMultiOutputSink * this) +{ + GST_OBJECT_FLAG_UNSET (this, GST_MULTI_OUTPUT_SINK_OPEN); + + CLIENTS_LOCK_INIT (this); + this->clients = NULL; + this->output_hash = g_hash_table_new (g_direct_hash, g_int_equal); + + this->bufqueue = g_array_new (FALSE, TRUE, sizeof (GstBuffer *)); + this->unit_type = DEFAULT_UNIT_TYPE; + this->units_max = DEFAULT_UNITS_MAX; + this->units_soft_max = DEFAULT_UNITS_SOFT_MAX; + this->time_min = DEFAULT_TIME_MIN; + this->bytes_min = DEFAULT_BYTES_MIN; + this->buffers_min = DEFAULT_BUFFERS_MIN; + this->recover_policy = DEFAULT_RECOVER_POLICY; + + this->timeout = DEFAULT_TIMEOUT; + this->def_sync_method = DEFAULT_SYNC_METHOD; + this->def_burst_format = DEFAULT_BURST_FORMAT; + this->def_burst_value = DEFAULT_BURST_VALUE; + + this->qos_dscp = DEFAULT_QOS_DSCP; + this->handle_read = DEFAULT_HANDLE_READ; + + this->resend_streamheader = DEFAULT_RESEND_STREAMHEADER; + + this->header_flags = 0; +#if 0 + this->cancellable = g_cancellable_new (); +#endif +} + +static void +gst_multi_output_sink_finalize (GObject * object) +{ + GstMultiOutputSink *this; + + this = GST_MULTI_OUTPUT_SINK (object); + + CLIENTS_LOCK_CLEAR (this); + g_hash_table_destroy (this->output_hash); + g_array_free (this->bufqueue, TRUE); + + if (this->cancellable) { + g_object_unref (this->cancellable); + this->cancellable = NULL; + } + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static gint +setup_dscp_client (GstMultiOutputSink * sink, GstOutputClient * client) +{ +#ifndef IP_TOS + return 0; +#else + gint tos; + gint ret; + int fd; + union gst_sockaddr + { + struct sockaddr sa; + struct sockaddr_in6 sa_in6; + struct sockaddr_storage sa_stor; + } sa; + socklen_t slen = sizeof (sa); + gint af; + + /* don't touch */ + if (sink->qos_dscp < 0) + return 0; + + fd = g_output_get_fd (client->output); + + if ((ret = getsockname (fd, &sa.sa, &slen)) < 0) { + GST_DEBUG_OBJECT (sink, "could not get sockname: %s", g_strerror (errno)); + return ret; + } + + af = sa.sa.sa_family; + + /* if this is an IPv4-mapped address then do IPv4 QoS */ + if (af == AF_INET6) { + + GST_DEBUG_OBJECT (sink, "check IP6 output"); + if (IN6_IS_ADDR_V4MAPPED (&(sa.sa_in6.sin6_addr))) { + GST_DEBUG_OBJECT (sink, "mapped to IPV4"); + af = AF_INET; + } + } + + /* extract and shift 6 bits of the DSCP */ + tos = (sink->qos_dscp & 0x3f) << 2; + + switch (af) { + case AF_INET: + ret = setsockopt (fd, IPPROTO_IP, IP_TOS, &tos, sizeof (tos)); + break; + case AF_INET6: +#ifdef IPV6_TCLASS + ret = setsockopt (fd, IPPROTO_IPV6, IPV6_TCLASS, &tos, sizeof (tos)); + break; +#endif + default: + ret = 0; + GST_ERROR_OBJECT (sink, "unsupported AF"); + break; + } + if (ret) + GST_DEBUG_OBJECT (sink, "could not set DSCP: %s", g_strerror (errno)); + + return ret; +#endif +} + +static void +setup_dscp (GstMultiOutputSink * sink) +{ + GList *clients; + + CLIENTS_LOCK (sink); + for (clients = sink->clients; clients; clients = clients->next) { + GstOutputClient *client; + + client = clients->data; + + setup_dscp_client (sink, client); + } + CLIENTS_UNLOCK (sink); +} + +/* "add-full" signal implementation */ +void +gst_multi_output_sink_add_full (GstMultiOutputSink * sink, GstOutput * output, + GstSyncMethod sync_method, GstFormat min_format, guint64 min_value, + GstFormat max_format, guint64 max_value) +{ + GstOutputClient *client; + GList *clink; + GTimeVal now; + + GST_DEBUG_OBJECT (sink, "[output %p] adding client, sync_method %d, " + "min_format %d, min_value %" G_GUINT64_FORMAT + ", max_format %d, max_value %" G_GUINT64_FORMAT, output, + sync_method, min_format, min_value, max_format, max_value); + + /* do limits check if we can */ + if (min_format == max_format) { + if (max_value != -1 && min_value != -1 && max_value < min_value) + goto wrong_limits; + } + + /* create client datastructure */ + client = g_new0 (GstOutputClient, 1); + client->output = G_OUTPUT (g_object_ref (output)); + client->status = GST_CLIENT_STATUS_OK; + client->bufpos = -1; + client->flushcount = -1; + client->bufoffset = 0; + client->sending = NULL; + client->bytes_sent = 0; + client->dropped_buffers = 0; + client->avg_queue_size = 0; + client->first_buffer_ts = GST_CLOCK_TIME_NONE; + client->last_buffer_ts = GST_CLOCK_TIME_NONE; + client->new_connection = TRUE; + client->burst_min_format = min_format; + client->burst_min_value = min_value; + client->burst_max_format = max_format; + client->burst_max_value = max_value; + client->sync_method = sync_method; + client->currently_removing = FALSE; + + /* update start time */ + g_get_current_time (&now); + client->connect_time = GST_TIMEVAL_TO_TIME (now); + client->disconnect_time = 0; + /* set last activity time to connect time */ + client->last_activity_time = client->connect_time; + + CLIENTS_LOCK (sink); + + /* check the hash to find a duplicate fd */ + clink = g_hash_table_lookup (sink->output_hash, output); + if (clink != NULL) + goto duplicate; + + /* we can add the fd now */ + clink = sink->clients = g_list_prepend (sink->clients, client); + g_hash_table_insert (sink->output_hash, output, clink); + sink->clients_cookie++; + + /* set the output to non blocking */ + g_output_set_blocking (output, FALSE); + + /* we always read from a client */ + if (sink->main_context) { + client->source = + g_output_create_source (client->output, + G_IO_IN | G_IO_OUT | G_IO_PRI | G_IO_ERR | G_IO_HUP, sink->cancellable); + g_source_set_callback (client->source, + (GSourceFunc) gst_multi_output_sink_output_condition, + gst_object_ref (sink), (GDestroyNotify) gst_object_unref); + g_source_attach (client->source, sink->main_context); + } + + setup_dscp_client (sink, client); + + CLIENTS_UNLOCK (sink); + + g_signal_emit (G_OBJECT (sink), + gst_multi_output_sink_signals[SIGNAL_CLIENT_ADDED], 0, output); + + return; + + /* errors */ +wrong_limits: + { + GST_WARNING_OBJECT (sink, + "[output %p] wrong values min =%" G_GUINT64_FORMAT ", max=%" + G_GUINT64_FORMAT ", format %d specified when adding client", output, + min_value, max_value, min_format); + return; + } +duplicate: + { + client->status = GST_CLIENT_STATUS_DUPLICATE; + CLIENTS_UNLOCK (sink); + GST_WARNING_OBJECT (sink, "[output %p] duplicate client found, refusing", + output); + g_signal_emit (G_OBJECT (sink), + gst_multi_output_sink_signals[SIGNAL_CLIENT_REMOVED], 0, output, + client->status); + g_free (client); + return; + } +} + +/* "add" signal implementation */ +void +gst_multi_output_sink_add (GstMultiOutputSink * sink, GstOutput * output) +{ + gst_multi_output_sink_add_full (sink, output, sink->def_sync_method, + sink->def_burst_format, sink->def_burst_value, sink->def_burst_format, + -1); +} + +/* "remove" signal implementation */ +void +gst_multi_output_sink_remove (GstMultiOutputSink * sink, GstOutput * output) +{ + GList *clink; + + GST_DEBUG_OBJECT (sink, "[output %p] removing client", output); + + CLIENTS_LOCK (sink); + clink = g_hash_table_lookup (sink->output_hash, output); + if (clink != NULL) { + GstOutputClient *client = clink->data; + + if (client->status != GST_CLIENT_STATUS_OK) { + GST_INFO_OBJECT (sink, + "[output %p] Client already disconnecting with status %d", + output, client->status); + goto done; + } + + client->status = GST_CLIENT_STATUS_REMOVED; + gst_multi_output_sink_remove_client_link (sink, clink); + } else { + GST_WARNING_OBJECT (sink, "[output %p] no client with this output found!", + output); + } + +done: + CLIENTS_UNLOCK (sink); +} + +/* "remove-flush" signal implementation */ +void +gst_multi_output_sink_remove_flush (GstMultiOutputSink * sink, + GstOutput * output) +{ + GList *clink; + + GST_DEBUG_OBJECT (sink, "[output %p] flushing client", output); + + CLIENTS_LOCK (sink); + clink = g_hash_table_lookup (sink->output_hash, output); + if (clink != NULL) { + GstOutputClient *client = clink->data; + + if (client->status != GST_CLIENT_STATUS_OK) { + GST_INFO_OBJECT (sink, + "[output %p] Client already disconnecting with status %d", + output, client->status); + goto done; + } + + /* take the position of the client as the number of buffers left to flush. + * If the client was at position -1, we flush 0 buffers, 0 == flush 1 + * buffer, etc... */ + client->flushcount = client->bufpos + 1; + /* mark client as flushing. We can not remove the client right away because + * it might have some buffers to flush in the ->sending queue. */ + client->status = GST_CLIENT_STATUS_FLUSHING; + } else { + GST_WARNING_OBJECT (sink, "[output %p] no client with this fd found!", + output); + } +done: + CLIENTS_UNLOCK (sink); +} + +/* can be called both through the signal (i.e. from any thread) or when + * stopping, after the writing thread has shut down */ +void +gst_multi_output_sink_clear (GstMultiOutputSink * sink) +{ + GList *clients; + guint32 cookie; + + GST_DEBUG_OBJECT (sink, "clearing all clients"); + + CLIENTS_LOCK (sink); +restart: + cookie = sink->clients_cookie; + for (clients = sink->clients; clients; clients = clients->next) { + GstOutputClient *client; + + if (cookie != sink->clients_cookie) { + GST_DEBUG_OBJECT (sink, "cookie changed while removing all clients"); + goto restart; + } + + client = clients->data; + client->status = GST_CLIENT_STATUS_REMOVED; + gst_multi_output_sink_remove_client_link (sink, clients); + } + + CLIENTS_UNLOCK (sink); +} + +/* "get-stats" signal implementation + */ +GstStructure * +gst_multi_output_sink_get_stats (GstMultiOutputSink * sink, GstOutput * output) +{ + GstOutputClient *client; + GstStructure *result = NULL; + GList *clink; + + CLIENTS_LOCK (sink); + clink = g_hash_table_lookup (sink->output_hash, output); + if (clink == NULL) + goto noclient; + + client = clink->data; + if (client != NULL) { + guint64 interval; + + result = gst_structure_new_empty ("multioutputsink-stats"); + + if (client->disconnect_time == 0) { + GTimeVal nowtv; + + g_get_current_time (&nowtv); + + interval = GST_TIMEVAL_TO_TIME (nowtv) - client->connect_time; + } else { + interval = client->disconnect_time - client->connect_time; + } + + gst_structure_set (result, + "bytes-sent", G_TYPE_UINT64, client->bytes_sent, + "connect-time", G_TYPE_UINT64, client->connect_time, + "disconnect-time", G_TYPE_UINT64, client->disconnect_time, + "connected-duration", G_TYPE_UINT64, interval, + "last-activatity-time", G_TYPE_UINT64, client->last_activity_time, + "dropped-buffers", G_TYPE_UINT64, client->dropped_buffers, + "first-buffer-ts", G_TYPE_UINT64, client->first_buffer_ts, + "last-buffer-ts", G_TYPE_UINT64, client->last_buffer_ts, NULL); + } + +noclient: + CLIENTS_UNLOCK (sink); + + /* python doesn't like a NULL pointer yet */ + if (result == NULL) { + GST_WARNING_OBJECT (sink, "[output %p] no client with this found!", output); + result = gst_structure_new_empty ("multioutputsink-stats"); + } + + return result; +} + +/* should be called with the clientslock held. + * Note that we don't close the fd as we didn't open it in the first + * place. An application should connect to the client-fd-removed signal and + * close the fd itself. + */ +static void +gst_multi_output_sink_remove_client_link (GstMultiOutputSink * sink, + GList * link) +{ + GstOutput *output; + GTimeVal now; + GstOutputClient *client = link->data; + GstMultiOutputSinkClass *fclass; + + fclass = GST_MULTI_OUTPUT_SINK_GET_CLASS (sink); + + output = client->output; + + if (client->currently_removing) { + GST_WARNING_OBJECT (sink, "[output %p] client is already being removed", + output); + return; + } else { + client->currently_removing = TRUE; + } + + /* FIXME: if we keep track of ip we can log it here and signal */ + switch (client->status) { + case GST_CLIENT_STATUS_OK: + GST_WARNING_OBJECT (sink, "[output %p] removing client %p for no reason", + output, client); + break; + case GST_CLIENT_STATUS_CLOSED: + GST_DEBUG_OBJECT (sink, "[output %p] removing client %p because of close", + output, client); + break; + case GST_CLIENT_STATUS_REMOVED: + GST_DEBUG_OBJECT (sink, + "[output %p] removing client %p because the app removed it", output, + client); + break; + case GST_CLIENT_STATUS_SLOW: + GST_INFO_OBJECT (sink, + "[output %p] removing client %p because it was too slow", output, + client); + break; + case GST_CLIENT_STATUS_ERROR: + GST_WARNING_OBJECT (sink, + "[output %p] removing client %p because of error", output, client); + break; + case GST_CLIENT_STATUS_FLUSHING: + default: + GST_WARNING_OBJECT (sink, + "[output %p] removing client %p with invalid reason %d", output, + client, client->status); + break; + } + + // FIXME: convert to vfunc to cleanup a client + + fclass->delete_client (sink, client); + + g_get_current_time (&now); + client->disconnect_time = GST_TIMEVAL_TO_TIME (now); + + /* free client buffers */ + g_slist_foreach (client->sending, (GFunc) gst_mini_object_unref, NULL); + g_slist_free (client->sending); + client->sending = NULL; + + if (client->caps) + gst_caps_unref (client->caps); + client->caps = NULL; + + /* unlock the mutex before signaling because the signal handler + * might query some properties */ + CLIENTS_UNLOCK (sink); + + g_signal_emit (G_OBJECT (sink), + gst_multi_output_sink_signals[SIGNAL_CLIENT_REMOVED], 0, output, + client->status); + + /* lock again before we remove the client completely */ + CLIENTS_LOCK (sink); + + /* fd cannot be reused in the above signal callback so we can safely + * remove it from the hashtable here */ + if (!g_hash_table_remove (sink->output_hash, output)) { + GST_WARNING_OBJECT (sink, + "[output %p] error removing client %p from hash", output, client); + } + /* after releasing the lock above, the link could be invalid, more + * precisely, the next and prev pointers could point to invalid list + * links. One optimisation could be to add a cookie to the linked list + * and take a shortcut when it did not change between unlocking and locking + * our mutex. For now we just walk the list again. */ + sink->clients = g_list_remove (sink->clients, client); + sink->clients_cookie++; + + if (fclass->removed) + fclass->removed (sink, output); + + g_free (client); + CLIENTS_UNLOCK (sink); + + /* and the fd is really gone now */ + g_signal_emit (G_OBJECT (sink), + gst_multi_output_sink_signals[SIGNAL_CLIENT_OUTPUT_REMOVED], 0, output); + g_object_unref (output); + + CLIENTS_LOCK (sink); +} + +/* handle a read on a client output, + * which either indicates a close or should be ignored + * returns FALSE if some error occured or the client closed. */ +static gboolean +gst_multi_output_sink_handle_client_read (GstMultiOutputSink * sink, + GstOutputClient * client) +{ + gboolean ret; + gchar dummy[256]; + gssize nread; + GError *err = NULL; + gboolean first = TRUE; + + GST_DEBUG_OBJECT (sink, "[output %p] select reports client read", + client->output); + + ret = TRUE; + + /* just Read 'n' Drop, could also just drop the client as it's not supposed + * to write to us except for closing the output, I guess it's because we + * like to listen to our customers. */ + do { + gssize navail; + + GST_DEBUG_OBJECT (sink, "[output %p] client wants us to read", + client->output); + + navail = g_output_get_available_bytes (client->output); + if (navail < 0) + break; + + nread = + g_output_receive (client->output, dummy, MIN (navail, sizeof (dummy)), + sink->cancellable, &err); + if (first && nread == 0) { + /* client sent close, so remove it */ + GST_DEBUG_OBJECT (sink, "[output %p] client asked for close, removing", + client->output); + client->status = GST_CLIENT_STATUS_CLOSED; + ret = FALSE; + } else if (nread < 0) { + GST_WARNING_OBJECT (sink, "[output %p] could not read: %s", + client->output, err->message); + client->status = GST_CLIENT_STATUS_ERROR; + ret = FALSE; + break; + } + first = FALSE; + } while (nread > 0); + g_clear_error (&err); + + return ret; +} + +static gboolean +is_sync_frame (GstMultiOutputSink * sink, GstBuffer * buffer) +{ + if (GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT)) { + return FALSE; + } else if (!GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_IN_CAPS)) { + return TRUE; + } + + return FALSE; +} + +#if 0 +/* queue the given buffer for the given client */ +static gboolean +gst_multi_output_sink_client_queue_buffer (GstMultiOutputSink * sink, + GstOutputClient * client, GstBuffer * buffer) +{ + GstCaps *caps; + + /* TRUE: send them if the new caps have them */ + gboolean send_streamheader = FALSE; + GstStructure *s; + + /* before we queue the buffer, we check if we need to queue streamheader + * buffers (because it's a new client, or because they changed) */ + caps = gst_pad_get_current_caps (GST_BASE_SINK_PAD (sink)); + + if (!client->caps) { + GST_DEBUG_OBJECT (sink, + "[output %p] no previous caps for this client, send streamheader", + client->output); + send_streamheader = TRUE; + client->caps = gst_caps_ref (caps); + } else { + /* there were previous caps recorded, so compare */ + if (!gst_caps_is_equal (caps, client->caps)) { + const GValue *sh1, *sh2; + + /* caps are not equal, but could still have the same streamheader */ + s = gst_caps_get_structure (caps, 0); + if (!gst_structure_has_field (s, "streamheader")) { + /* no new streamheader, so nothing new to send */ + GST_DEBUG_OBJECT (sink, + "[output %p] new caps do not have streamheader, not sending", + client->output); + } else { + /* there is a new streamheader */ + s = gst_caps_get_structure (client->caps, 0); + if (!gst_structure_has_field (s, "streamheader")) { + /* no previous streamheader, so send the new one */ + GST_DEBUG_OBJECT (sink, + "[output %p] previous caps did not have streamheader, sending", + client->output); + send_streamheader = TRUE; + } else { + /* both old and new caps have streamheader set */ + if (!sink->resend_streamheader) { + GST_DEBUG_OBJECT (sink, + "[output %p] asked to not resend the streamheader, not sending", + client->output); + send_streamheader = FALSE; + } else { + sh1 = gst_structure_get_value (s, "streamheader"); + s = gst_caps_get_structure (caps, 0); + sh2 = gst_structure_get_value (s, "streamheader"); + if (gst_value_compare (sh1, sh2) != GST_VALUE_EQUAL) { + GST_DEBUG_OBJECT (sink, + "[output %p] new streamheader different from old, sending", + client->output); + send_streamheader = TRUE; + } + } + } + } + } + /* Replace the old caps */ + gst_caps_unref (client->caps); + client->caps = gst_caps_ref (caps); + } + + if (G_UNLIKELY (send_streamheader)) { + const GValue *sh; + GArray *buffers; + int i; + + GST_LOG_OBJECT (sink, + "[output %p] sending streamheader from caps %" GST_PTR_FORMAT, + client->output, caps); + s = gst_caps_get_structure (caps, 0); + if (!gst_structure_has_field (s, "streamheader")) { + GST_DEBUG_OBJECT (sink, + "[output %p] no new streamheader, so nothing to send", + client->output); + } else { + GST_LOG_OBJECT (sink, + "[output %p] sending streamheader from caps %" GST_PTR_FORMAT, + client->output, caps); + sh = gst_structure_get_value (s, "streamheader"); + g_assert (G_VALUE_TYPE (sh) == GST_TYPE_ARRAY); + buffers = g_value_peek_pointer (sh); + GST_DEBUG_OBJECT (sink, "%d streamheader buffers", buffers->len); + for (i = 0; i < buffers->len; ++i) { + GValue *bufval; + GstBuffer *buffer; + + bufval = &g_array_index (buffers, GValue, i); + g_assert (G_VALUE_TYPE (bufval) == GST_TYPE_BUFFER); + buffer = g_value_peek_pointer (bufval); + GST_DEBUG_OBJECT (sink, + "[output %p] queueing streamheader buffer of length %" + G_GSIZE_FORMAT, client->output, gst_buffer_get_size (buffer)); + gst_buffer_ref (buffer); + + client->sending = g_slist_append (client->sending, buffer); + } + } + } + + gst_caps_unref (caps); + caps = NULL; + + GST_LOG_OBJECT (sink, + "[output %p] queueing buffer of length %" G_GSIZE_FORMAT, client->output, + gst_buffer_get_size (buffer)); + + gst_buffer_ref (buffer); + client->sending = g_slist_append (client->sending, buffer); + + return TRUE; +} +#endif + +/* find the keyframe in the list of buffers starting the + * search from @idx. @direction as -1 will search backwards, + * 1 will search forwards. + * Returns: the index or -1 if there is no keyframe after idx. + */ +static gint +find_syncframe (GstMultiOutputSink * sink, gint idx, gint direction) +{ + gint i, len, result; + + /* take length of queued buffers */ + len = sink->bufqueue->len; + + /* assume we don't find a keyframe */ + result = -1; + + /* then loop over all buffers to find the first keyframe */ + for (i = idx; i >= 0 && i < len; i += direction) { + GstBuffer *buf; + + buf = g_array_index (sink->bufqueue, GstBuffer *, i); + if (is_sync_frame (sink, buf)) { + GST_LOG_OBJECT (sink, "found keyframe at %d from %d, direction %d", + i, idx, direction); + result = i; + break; + } + } + return result; +} + +#define find_next_syncframe(s,i) find_syncframe(s,i,1) +#define find_prev_syncframe(s,i) find_syncframe(s,i,-1) + +/* Get the number of buffers from the buffer queue needed to satisfy + * the maximum max in the configured units. + * If units are not BUFFERS, and there are insufficient buffers in the + * queue to satify the limit, return len(queue) + 1 */ +static gint +get_buffers_max (GstMultiOutputSink * sink, gint64 max) +{ + switch (sink->unit_type) { + case GST_FORMAT_BUFFERS: + return max; + case GST_FORMAT_TIME: + { + GstBuffer *buf; + int i; + int len; + gint64 diff; + GstClockTime first = GST_CLOCK_TIME_NONE; + + len = sink->bufqueue->len; + + for (i = 0; i < len; i++) { + buf = g_array_index (sink->bufqueue, GstBuffer *, i); + if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) { + if (first == -1) + first = GST_BUFFER_TIMESTAMP (buf); + + diff = first - GST_BUFFER_TIMESTAMP (buf); + + if (diff > max) + return i + 1; + } + } + return len + 1; + } + case GST_FORMAT_BYTES: + { + GstBuffer *buf; + int i; + int len; + gint acc = 0; + + len = sink->bufqueue->len; + + for (i = 0; i < len; i++) { + buf = g_array_index (sink->bufqueue, GstBuffer *, i); + acc += gst_buffer_get_size (buf); + + if (acc > max) + return i + 1; + } + return len + 1; + } + default: + return max; + } +} + +/* find the positions in the buffer queue where *_min and *_max + * is satisfied + */ +/* count the amount of data in the buffers and return the index + * that satifies the given limits. + * + * Returns: index @idx in the buffer queue so that the given limits are + * satisfied. TRUE if all the limits could be satisfied, FALSE if not + * enough data was in the queue. + * + * FIXME, this code might now work if any of the units is in buffers... + */ +static gboolean +find_limits (GstMultiOutputSink * sink, + gint * min_idx, gint bytes_min, gint buffers_min, gint64 time_min, + gint * max_idx, gint bytes_max, gint buffers_max, gint64 time_max) +{ + GstClockTime first, time; + gint i, len, bytes; + gboolean result, max_hit; + + /* take length of queue */ + len = sink->bufqueue->len; + + /* this must hold */ + g_assert (len > 0); + + GST_LOG_OBJECT (sink, + "bytes_min %d, buffers_min %d, time_min %" GST_TIME_FORMAT + ", bytes_max %d, buffers_max %d, time_max %" GST_TIME_FORMAT, bytes_min, + buffers_min, GST_TIME_ARGS (time_min), bytes_max, buffers_max, + GST_TIME_ARGS (time_max)); + + /* do the trivial buffer limit test */ + if (buffers_min != -1 && len < buffers_min) { + *min_idx = len - 1; + *max_idx = len - 1; + return FALSE; + } + + result = FALSE; + /* else count bytes and time */ + first = -1; + bytes = 0; + /* unset limits */ + *min_idx = -1; + *max_idx = -1; + max_hit = FALSE; + + i = 0; + /* loop through the buffers, when a limit is ok, mark it + * as -1, we have at least one buffer in the queue. */ + do { + GstBuffer *buf; + + /* if we checked all min limits, update result */ + if (bytes_min == -1 && time_min == -1 && *min_idx == -1) { + /* don't go below 0 */ + *min_idx = MAX (i - 1, 0); + } + /* if we reached one max limit break out */ + if (max_hit) { + /* i > 0 when we get here, we subtract one to get the position + * of the previous buffer. */ + *max_idx = i - 1; + /* we have valid complete result if we found a min_idx too */ + result = *min_idx != -1; + break; + } + buf = g_array_index (sink->bufqueue, GstBuffer *, i); + + bytes += gst_buffer_get_size (buf); + + /* take timestamp and save for the base first timestamp */ + if ((time = GST_BUFFER_TIMESTAMP (buf)) != -1) { + GST_LOG_OBJECT (sink, "Ts %" GST_TIME_FORMAT " on buffer", + GST_TIME_ARGS (time)); + if (first == -1) + first = time; + + /* increase max usage if we did not fill enough. Note that + * buffers are sorted from new to old, so the first timestamp is + * bigger than the next one. */ + if (time_min != -1 && first - time >= time_min) + time_min = -1; + if (time_max != -1 && first - time >= time_max) + max_hit = TRUE; + } else { + GST_LOG_OBJECT (sink, "No timestamp on buffer"); + } + /* time is OK or unknown, check and increase if not enough bytes */ + if (bytes_min != -1) { + if (bytes >= bytes_min) + bytes_min = -1; + } + if (bytes_max != -1) { + if (bytes >= bytes_max) { + max_hit = TRUE; + } + } + i++; + } + while (i < len); + + /* if we did not hit the max or min limit, set to buffer size */ + if (*max_idx == -1) + *max_idx = len - 1; + /* make sure min does not exceed max */ + if (*min_idx == -1) + *min_idx = *max_idx; + + return result; +} + +/* parse the unit/value pair and assign it to the result value of the + * right type, leave the other values untouched + * + * Returns: FALSE if the unit is unknown or undefined. TRUE otherwise. + */ +static gboolean +assign_value (GstFormat format, guint64 value, gint * bytes, gint * buffers, + GstClockTime * time) +{ + gboolean res = TRUE; + + /* set only the limit of the given format to the given value */ + switch (format) { + case GST_FORMAT_BUFFERS: + *buffers = (gint) value; + break; + case GST_FORMAT_TIME: + *time = value; + break; + case GST_FORMAT_BYTES: + *bytes = (gint) value; + break; + case GST_FORMAT_UNDEFINED: + default: + res = FALSE; + break; + } + return res; +} + +/* count the index in the buffer queue to satisfy the given unit + * and value pair starting from buffer at index 0. + * + * Returns: TRUE if there was enough data in the queue to satisfy the + * burst values. @idx contains the index in the buffer that contains enough + * data to satisfy the limits or the last buffer in the queue when the + * function returns FALSE. + */ +static gboolean +count_burst_unit (GstMultiOutputSink * sink, gint * min_idx, + GstFormat min_format, guint64 min_value, gint * max_idx, + GstFormat max_format, guint64 max_value) +{ + gint bytes_min = -1, buffers_min = -1; + gint bytes_max = -1, buffers_max = -1; + GstClockTime time_min = GST_CLOCK_TIME_NONE, time_max = GST_CLOCK_TIME_NONE; + + assign_value (min_format, min_value, &bytes_min, &buffers_min, &time_min); + assign_value (max_format, max_value, &bytes_max, &buffers_max, &time_max); + + return find_limits (sink, min_idx, bytes_min, buffers_min, time_min, + max_idx, bytes_max, buffers_max, time_max); +} + +/* decide where in the current buffer queue this new client should start + * receiving buffers from. + * This function is called whenever a client is connected and has not yet + * received a buffer. + * If this returns -1, it means that we haven't found a good point to + * start streaming from yet, and this function should be called again later + * when more buffers have arrived. + */ +static gint +gst_multi_output_sink_new_client (GstMultiOutputSink * sink, + GstOutputClient * client) +{ + gint result; + + GST_DEBUG_OBJECT (sink, + "[output %p] new client, deciding where to start in queue", + client->output); + GST_DEBUG_OBJECT (sink, "queue is currently %d buffers long", + sink->bufqueue->len); + switch (client->sync_method) { + case GST_SYNC_METHOD_LATEST: + /* no syncing, we are happy with whatever the client is going to get */ + result = client->bufpos; + GST_DEBUG_OBJECT (sink, + "[output %p] SYNC_METHOD_LATEST, position %d", client->output, + result); + break; + case GST_SYNC_METHOD_NEXT_KEYFRAME: + { + /* if one of the new buffers (between client->bufpos and 0) in the queue + * is a sync point, we can proceed, otherwise we need to keep waiting */ + GST_LOG_OBJECT (sink, + "[output %p] new client, bufpos %d, waiting for keyframe", + client->output, client->bufpos); + + result = find_prev_syncframe (sink, client->bufpos); + if (result != -1) { + GST_DEBUG_OBJECT (sink, + "[output %p] SYNC_METHOD_NEXT_KEYFRAME: result %d", + client->output, result); + break; + } + + /* client is not on a syncbuffer, need to skip these buffers and + * wait some more */ + GST_LOG_OBJECT (sink, + "[output %p] new client, skipping buffer(s), no syncpoint found", + client->output); + client->bufpos = -1; + break; + } + case GST_SYNC_METHOD_LATEST_KEYFRAME: + { + GST_DEBUG_OBJECT (sink, + "[output %p] SYNC_METHOD_LATEST_KEYFRAME", client->output); + + /* for new clients we initially scan the complete buffer queue for + * a sync point when a buffer is added. If we don't find a keyframe, + * we need to wait for the next keyframe and so we change the client's + * sync method to GST_SYNC_METHOD_NEXT_KEYFRAME. + */ + result = find_next_syncframe (sink, 0); + if (result != -1) { + GST_DEBUG_OBJECT (sink, + "[output %p] SYNC_METHOD_LATEST_KEYFRAME: result %d", + client->output, result); + break; + } + + GST_DEBUG_OBJECT (sink, + "[output %p] SYNC_METHOD_LATEST_KEYFRAME: no keyframe found, " + "switching to SYNC_METHOD_NEXT_KEYFRAME", client->output); + /* throw client to the waiting state */ + client->bufpos = -1; + /* and make client sync to next keyframe */ + client->sync_method = GST_SYNC_METHOD_NEXT_KEYFRAME; + break; + } + case GST_SYNC_METHOD_BURST: + { + gboolean ok; + gint max; + + /* move to the position where we satisfy the client's burst + * parameters. If we could not satisfy the parameters because there + * is not enough data, we just send what we have (which is in result). + * We use the max value to limit the search + */ + ok = count_burst_unit (sink, &result, client->burst_min_format, + client->burst_min_value, &max, client->burst_max_format, + client->burst_max_value); + GST_DEBUG_OBJECT (sink, + "[output %p] SYNC_METHOD_BURST: burst_unit returned %d, result %d", + client->output, ok, result); + + GST_LOG_OBJECT (sink, "min %d, max %d", result, max); + + /* we hit the max and it is below the min, use that then */ + if (max != -1 && max <= result) { + result = MAX (max - 1, 0); + GST_DEBUG_OBJECT (sink, + "[output %p] SYNC_METHOD_BURST: result above max, taken down to %d", + client->output, result); + } + break; + } + case GST_SYNC_METHOD_BURST_KEYFRAME: + { + gint min_idx, max_idx; + gint next_syncframe, prev_syncframe; + + /* BURST_KEYFRAME: + * + * _always_ start sending a keyframe to the client. We first search + * a keyframe between min/max limits. If there is none, we send it the + * last keyframe before min. If there is none, the behaviour is like + * NEXT_KEYFRAME. + */ + /* gather burst limits */ + count_burst_unit (sink, &min_idx, client->burst_min_format, + client->burst_min_value, &max_idx, client->burst_max_format, + client->burst_max_value); + + GST_LOG_OBJECT (sink, "min %d, max %d", min_idx, max_idx); + + /* first find a keyframe after min_idx */ + next_syncframe = find_next_syncframe (sink, min_idx); + if (next_syncframe != -1 && next_syncframe < max_idx) { + /* we have a valid keyframe and it's below the max */ + GST_LOG_OBJECT (sink, "found keyframe in min/max limits"); + result = next_syncframe; + break; + } + + /* no valid keyframe, try to find one below min */ + prev_syncframe = find_prev_syncframe (sink, min_idx); + if (prev_syncframe != -1) { + GST_WARNING_OBJECT (sink, + "using keyframe below min in BURST_KEYFRAME sync mode"); + result = prev_syncframe; + break; + } + + /* no prev keyframe or not enough data */ + GST_WARNING_OBJECT (sink, + "no prev keyframe found in BURST_KEYFRAME sync mode, waiting for next"); + + /* throw client to the waiting state */ + client->bufpos = -1; + /* and make client sync to next keyframe */ + client->sync_method = GST_SYNC_METHOD_NEXT_KEYFRAME; + result = -1; + break; + } + case GST_SYNC_METHOD_BURST_WITH_KEYFRAME: + { + gint min_idx, max_idx; + gint next_syncframe; + + /* BURST_WITH_KEYFRAME: + * + * try to start sending a keyframe to the client. We first search + * a keyframe between min/max limits. If there is none, we send it the + * amount of data up 'till min. + */ + /* gather enough data to burst */ + count_burst_unit (sink, &min_idx, client->burst_min_format, + client->burst_min_value, &max_idx, client->burst_max_format, + client->burst_max_value); + + GST_LOG_OBJECT (sink, "min %d, max %d", min_idx, max_idx); + + /* first find a keyframe after min_idx */ + next_syncframe = find_next_syncframe (sink, min_idx); + if (next_syncframe != -1 && next_syncframe < max_idx) { + /* we have a valid keyframe and it's below the max */ + GST_LOG_OBJECT (sink, "found keyframe in min/max limits"); + result = next_syncframe; + break; + } + + /* no keyframe, send data from min_idx */ + GST_WARNING_OBJECT (sink, "using min in BURST_WITH_KEYFRAME sync mode"); + + /* make sure we don't go over the max limit */ + if (max_idx != -1 && max_idx <= min_idx) { + result = MAX (max_idx - 1, 0); + } else { + result = min_idx; + } + + break; + } + default: + g_warning ("unknown sync method %d", client->sync_method); + result = client->bufpos; + break; + } + return result; +} + +/* Handle a write on a client, + * which indicates a read request from a client. + * + * For each client we maintain a queue of GstBuffers that contain the raw bytes + * we need to send to the client. + * + * We first check to see if we need to send streamheaders. If so, we queue them. + * + * Then we run into the main loop that tries to send as many buffers as + * possible. It will first exhaust the client->sending queue and if the queue + * is empty, it will pick a buffer from the global queue. + * + * Sending the buffers from the client->sending queue is basically writing + * the bytes to the output and maintaining a count of the bytes that were + * sent. When the buffer is completely sent, it is removed from the + * client->sending queue and we try to pick a new buffer for sending. + * + * When the sending returns a partial buffer we stop sending more data as + * the next send operation could block. + * + * This functions returns FALSE if some error occured. + */ +static gboolean +gst_multi_output_sink_handle_client_write (GstMultiOutputSink * sink, + GstOutputClient * client) +{ + GstOutput *output = client->output; + gboolean more; + gboolean flushing; + GstClockTime now; + GTimeVal nowtv; + GError *err = NULL; + + g_get_current_time (&nowtv); + now = GST_TIMEVAL_TO_TIME (nowtv); + + flushing = client->status == GST_CLIENT_STATUS_FLUSHING; + + more = TRUE; + do { + gint maxsize; + + if (!client->sending) { + /* client is not working on a buffer */ + if (client->bufpos == -1) { + /* client is too fast, remove from write queue until new buffer is + * available */ + if (client->source) { + g_source_destroy (client->source); + g_source_unref (client->source); + client->source = NULL; + } + /* if we flushed out all of the client buffers, we can stop */ + if (client->flushcount == 0) + goto flushed; + + return TRUE; + } else { + /* client can pick a buffer from the global queue */ + GstBuffer *buf; + GstClockTime timestamp; + + /* for new connections, we need to find a good spot in the + * bufqueue to start streaming from */ + if (client->new_connection && !flushing) { + gint position = gst_multi_output_sink_new_client (sink, client); + + if (position >= 0) { + /* we got a valid spot in the queue */ + client->new_connection = FALSE; + client->bufpos = position; + } else { + /* cannot send data to this client yet */ + if (client->source) { + g_source_destroy (client->source); + g_source_unref (client->source); + client->source = NULL; + } + return TRUE; + } + } + + /* we flushed all remaining buffers, no need to get a new one */ + if (client->flushcount == 0) + goto flushed; + + /* grab buffer */ + buf = g_array_index (sink->bufqueue, GstBuffer *, client->bufpos); + client->bufpos--; + + /* update stats */ + timestamp = GST_BUFFER_TIMESTAMP (buf); + if (client->first_buffer_ts == GST_CLOCK_TIME_NONE) + client->first_buffer_ts = timestamp; + if (timestamp != -1) + client->last_buffer_ts = timestamp; + + /* decrease flushcount */ + if (client->flushcount != -1) + client->flushcount--; + + GST_LOG_OBJECT (sink, "[output %p] client %p at position %d", + output, client, client->bufpos); + + /* queueing a buffer will ref it */ + gst_multi_output_sink_client_queue_buffer (sink, client, buf); + + /* need to start from the first byte for this new buffer */ + client->bufoffset = 0; + } + } + + /* see if we need to send something */ + if (client->sending) { + gssize wrote; + GstBuffer *head; + GstMapInfo map; + + /* pick first buffer from list */ + head = GST_BUFFER (client->sending->data); + + gst_buffer_map (head, &map, GST_MAP_READ); + maxsize = map.size - client->bufoffset; + + /* try to write the complete buffer */ + + wrote = + g_output_send (output, (gchar *) map.data + client->bufoffset, + maxsize, sink->cancellable, &err); + gst_buffer_unmap (head, &map); + + if (wrote < 0) { + /* hmm error.. */ + if (g_error_matches (err, G_IO_ERROR, G_IO_ERROR_CLOSED)) { + goto connection_reset; + } else { + goto write_error; + } + } else { + if (wrote < maxsize) { + /* partial write means that the client cannot read more and we should + * stop sending more */ + GST_LOG_OBJECT (sink, + "partial write on %p of %" G_GSSIZE_FORMAT " bytes", output, + wrote); + client->bufoffset += wrote; + more = FALSE; + } else { + /* complete buffer was written, we can proceed to the next one */ + client->sending = g_slist_remove (client->sending, head); + gst_buffer_unref (head); + /* make sure we start from byte 0 for the next buffer */ + client->bufoffset = 0; + } + /* update stats */ + client->bytes_sent += wrote; + client->last_activity_time = now; + sink->bytes_served += wrote; + } + } + } while (more); + + return TRUE; + + /* ERRORS */ +flushed: + { + GST_DEBUG_OBJECT (sink, "[output %p] flushed, removing", output); + client->status = GST_CLIENT_STATUS_REMOVED; + return FALSE; + } +connection_reset: + { + GST_DEBUG_OBJECT (sink, "[output %p] connection reset by peer, removing", + output); + client->status = GST_CLIENT_STATUS_CLOSED; + g_clear_error (&err); + return FALSE; + } +write_error: + { + GST_WARNING_OBJECT (sink, + "[output %p] could not write, removing client: %s", output, + err->message); + g_clear_error (&err); + client->status = GST_CLIENT_STATUS_ERROR; + return FALSE; + } +} + +/* calculate the new position for a client after recovery. This function + * does not update the client position but merely returns the required + * position. + */ +static gint +gst_multi_output_sink_recover_client (GstMultiOutputSink * sink, + GstOutputClient * client) +{ + gint newbufpos; + + GST_WARNING_OBJECT (sink, + "[output %p] client %p is lagging at %d, recover using policy %d", + client->output, client, client->bufpos, sink->recover_policy); + + switch (sink->recover_policy) { + case GST_RECOVER_POLICY_NONE: + /* do nothing, client will catch up or get kicked out when it reaches + * the hard max */ + newbufpos = client->bufpos; + break; + case GST_RECOVER_POLICY_RESYNC_LATEST: + /* move to beginning of queue */ + newbufpos = -1; + break; + case GST_RECOVER_POLICY_RESYNC_SOFT_LIMIT: + /* move to beginning of soft max */ + newbufpos = get_buffers_max (sink, sink->units_soft_max); + break; + case GST_RECOVER_POLICY_RESYNC_KEYFRAME: + /* find keyframe in buffers, we search backwards to find the + * closest keyframe relative to what this client already received. */ + newbufpos = MIN (sink->bufqueue->len - 1, + get_buffers_max (sink, sink->units_soft_max) - 1); + + while (newbufpos >= 0) { + GstBuffer *buf; + + buf = g_array_index (sink->bufqueue, GstBuffer *, newbufpos); + if (is_sync_frame (sink, buf)) { + /* found a buffer that is not a delta unit */ + break; + } + newbufpos--; + } + break; + default: + /* unknown recovery procedure */ + newbufpos = get_buffers_max (sink, sink->units_soft_max); + break; + } + return newbufpos; +} + +/* Queue a buffer on the global queue. + * + * This function adds the buffer to the front of a GArray. It removes the + * tail buffer if the max queue size is exceeded, unreffing the queued buffer. + * Note that unreffing the buffer is not a problem as clients who + * started writing out this buffer will still have a reference to it in the + * client->sending queue. + * + * After adding the buffer, we update all client positions in the queue. If + * a client moves over the soft max, we start the recovery procedure for this + * slow client. If it goes over the hard max, it is put into the slow list + * and removed. + * + * Special care is taken of clients that were waiting for a new buffer (they + * had a position of -1) because they can proceed after adding this new buffer. + * This is done by adding the client back into the write fd_set and signaling + * the select thread that the fd_set changed. + */ +static void +gst_multi_output_sink_queue_buffer (GstMultiOutputSink * sink, GstBuffer * buf) +{ + GList *clients, *next; + gint queuelen; + gint max_buffer_usage; + gint i; + GTimeVal nowtv; + GstClockTime now; + gint max_buffers, soft_max_buffers; + guint cookie; + + g_get_current_time (&nowtv); + now = GST_TIMEVAL_TO_TIME (nowtv); + + CLIENTS_LOCK (sink); + /* add buffer to queue */ + gst_buffer_ref (buf); + g_array_prepend_val (sink->bufqueue, buf); + queuelen = sink->bufqueue->len; + + if (sink->units_max > 0) + max_buffers = get_buffers_max (sink, sink->units_max); + else + max_buffers = -1; + + if (sink->units_soft_max > 0) + soft_max_buffers = get_buffers_max (sink, sink->units_soft_max); + else + soft_max_buffers = -1; + GST_LOG_OBJECT (sink, "Using max %d, softmax %d", max_buffers, + soft_max_buffers); + + /* then loop over the clients and update the positions */ + max_buffer_usage = 0; + +restart: + cookie = sink->clients_cookie; + for (clients = sink->clients; clients; clients = next) { + GstOutputClient *client; + + if (cookie != sink->clients_cookie) { + GST_DEBUG_OBJECT (sink, "Clients cookie outdated, restarting"); + goto restart; + } + + client = clients->data; + next = g_list_next (clients); + + client->bufpos++; + GST_LOG_OBJECT (sink, "[output %p] client %p at position %d", + client->output, client, client->bufpos); + /* check soft max if needed, recover client */ + if (soft_max_buffers > 0 && client->bufpos >= soft_max_buffers) { + gint newpos; + + newpos = gst_multi_output_sink_recover_client (sink, client); + if (newpos != client->bufpos) { + client->dropped_buffers += client->bufpos - newpos; + client->bufpos = newpos; + client->discont = TRUE; + GST_INFO_OBJECT (sink, "[output %p] client %p position reset to %d", + client->output, client, client->bufpos); + } else { + GST_INFO_OBJECT (sink, + "[output %p] client %p not recovering position", + client->output, client); + } + } + /* check hard max and timeout, remove client */ + if ((max_buffers > 0 && client->bufpos >= max_buffers) || + (sink->timeout > 0 + && now - client->last_activity_time > sink->timeout)) { + /* remove client */ + GST_WARNING_OBJECT (sink, "[output %p] client %p is too slow, removing", + client->output, client); + /* remove the client, the fd set will be cleared and the select thread + * will be signaled */ + client->status = GST_CLIENT_STATUS_SLOW; + /* set client to invalid position while being removed */ + client->bufpos = -1; + gst_multi_output_sink_remove_client_link (sink, clients); + continue; + } else if (client->bufpos == 0 || client->new_connection) { + /* can send data to this client now. need to signal the select thread that + * the fd_set changed */ + if (!client->source) { + client->source = + g_output_create_source (client->output, + G_IO_IN | G_IO_OUT | G_IO_PRI | G_IO_ERR | G_IO_HUP, + sink->cancellable); + g_source_set_callback (client->source, + (GSourceFunc) gst_multi_output_sink_output_condition, + gst_object_ref (sink), (GDestroyNotify) gst_object_unref); + g_source_attach (client->source, sink->main_context); + } + } + /* keep track of maximum buffer usage */ + if (client->bufpos > max_buffer_usage) { + max_buffer_usage = client->bufpos; + } + } + + /* make sure we respect bytes-min, buffers-min and time-min when they are set */ + { + gint usage, max; + + GST_LOG_OBJECT (sink, + "extending queue %d to respect time_min %" GST_TIME_FORMAT + ", bytes_min %d, buffers_min %d", max_buffer_usage, + GST_TIME_ARGS (sink->time_min), sink->bytes_min, sink->buffers_min); + + /* get index where the limits are ok, we don't really care if all limits + * are ok, we just queue as much as we need. We also don't compare against + * the max limits. */ + find_limits (sink, &usage, sink->bytes_min, sink->buffers_min, + sink->time_min, &max, -1, -1, -1); + + max_buffer_usage = MAX (max_buffer_usage, usage + 1); + GST_LOG_OBJECT (sink, "extended queue to %d", max_buffer_usage); + } + + /* now look for sync points and make sure there is at least one + * sync point in the queue. We only do this if the LATEST_KEYFRAME or + * BURST_KEYFRAME mode is selected */ + if (sink->def_sync_method == GST_SYNC_METHOD_LATEST_KEYFRAME || + sink->def_sync_method == GST_SYNC_METHOD_BURST_KEYFRAME) { + /* no point in searching beyond the queue length */ + gint limit = queuelen; + GstBuffer *buf; + + /* no point in searching beyond the soft-max if any. */ + if (soft_max_buffers > 0) { + limit = MIN (limit, soft_max_buffers); + } + GST_LOG_OBJECT (sink, + "extending queue to include sync point, now at %d, limit is %d", + max_buffer_usage, limit); + for (i = 0; i < limit; i++) { + buf = g_array_index (sink->bufqueue, GstBuffer *, i); + if (is_sync_frame (sink, buf)) { + /* found a sync frame, now extend the buffer usage to + * include at least this frame. */ + max_buffer_usage = MAX (max_buffer_usage, i); + break; + } + } + GST_LOG_OBJECT (sink, "max buffer usage is now %d", max_buffer_usage); + } + + GST_LOG_OBJECT (sink, "len %d, usage %d", queuelen, max_buffer_usage); + + /* nobody is referencing units after max_buffer_usage so we can + * remove them from the queue. We remove them in reverse order as + * this is the most optimal for GArray. */ + for (i = queuelen - 1; i > max_buffer_usage; i--) { + GstBuffer *old; + + /* queue exceeded max size */ + queuelen--; + old = g_array_index (sink->bufqueue, GstBuffer *, i); + sink->bufqueue = g_array_remove_index (sink->bufqueue, i); + + /* unref tail buffer */ + gst_buffer_unref (old); + } + /* save for stats */ + sink->buffers_queued = max_buffer_usage; + CLIENTS_UNLOCK (sink); +} + +/* Handle the clients. This is called when a output becomes ready + * to read or writable. Badly behaving clients are put on a + * garbage list and removed. + */ +static gboolean +gst_multi_output_sink_output_condition (GstOutput * output, + GIOCondition condition, GstMultiOutputSink * sink) +{ + GList *clink; + GstOutputClient *client; + gboolean ret = TRUE; + + CLIENTS_LOCK (sink); + clink = g_hash_table_lookup (sink->output_hash, output); + if (clink == NULL) { + ret = FALSE; + goto done; + } + + client = clink->data; + + if (client->status != GST_CLIENT_STATUS_FLUSHING + && client->status != GST_CLIENT_STATUS_OK) { + gst_multi_output_sink_remove_client_link (sink, clink); + ret = FALSE; + goto done; + } + + if ((condition & G_IO_ERR)) { + GST_WARNING_OBJECT (sink, "Output %p has error", client->output); + client->status = GST_CLIENT_STATUS_ERROR; + gst_multi_output_sink_remove_client_link (sink, clink); + ret = FALSE; + goto done; + } else if ((condition & G_IO_HUP)) { + client->status = GST_CLIENT_STATUS_CLOSED; + gst_multi_output_sink_remove_client_link (sink, clink); + ret = FALSE; + goto done; + } else if ((condition & G_IO_IN) || (condition & G_IO_PRI)) { + /* handle client read */ + if (!gst_multi_output_sink_handle_client_read (sink, client)) { + gst_multi_output_sink_remove_client_link (sink, clink); + ret = FALSE; + goto done; + } + } else if ((condition & G_IO_OUT)) { + /* handle client write */ + if (!gst_multi_output_sink_handle_client_write (sink, client)) { + gst_multi_output_sink_remove_client_link (sink, clink); + ret = FALSE; + goto done; + } + } + +done: + CLIENTS_UNLOCK (sink); + + return ret; +} + +static gboolean +gst_multi_output_sink_timeout (GstMultiOutputSink * sink) +{ + GstClockTime now; + GTimeVal nowtv; + GList *clients; + + g_get_current_time (&nowtv); + now = GST_TIMEVAL_TO_TIME (nowtv); + + CLIENTS_LOCK (sink); + for (clients = sink->clients; clients; clients = clients->next) { + GstOutputClient *client; + + client = clients->data; + if (sink->timeout > 0 && now - client->last_activity_time > sink->timeout) { + client->status = GST_CLIENT_STATUS_SLOW; + gst_multi_output_sink_remove_client_link (sink, clients); + } + } + CLIENTS_UNLOCK (sink); + + return FALSE; +} + +/* we handle the client communication in another thread so that we do not block + * the gstreamer thread while we select() on the client fds */ +static gpointer +gst_multi_output_sink_thread (GstMultiOutputSink * sink) +{ + GSource *timeout = NULL; + + while (sink->running) { + if (sink->timeout > 0) { + timeout = g_timeout_source_new (sink->timeout / GST_MSECOND); + + g_source_set_callback (timeout, + (GSourceFunc) gst_multi_output_sink_timeout, gst_object_ref (sink), + (GDestroyNotify) gst_object_unref); + g_source_attach (timeout, sink->main_context); + } + + /* Returns after handling all pending events or when + * _wakeup() was called. In any case we have to add + * a new timeout because something happened. + */ + g_main_context_iteration (sink->main_context, TRUE); + + if (timeout) { + g_source_destroy (timeout); + g_source_unref (timeout); + } + } + + return NULL; +} + +static GstFlowReturn +gst_multi_output_sink_render (GstBaseSink * bsink, GstBuffer * buf) +{ + GstMultiOutputSink *sink; + gboolean in_caps; +#if 0 + GstCaps *bufcaps, *padcaps; +#endif + + sink = GST_MULTI_OUTPUT_SINK (bsink); + + g_return_val_if_fail (GST_OBJECT_FLAG_IS_SET (sink, + GST_MULTI_OUTPUT_SINK_OPEN), GST_FLOW_WRONG_STATE); + +#if 0 + /* since we check every buffer for streamheader caps, we need to make + * sure every buffer has caps set */ + bufcaps = gst_buffer_get_caps (buf); + padcaps = GST_PAD_CAPS (GST_BASE_SINK_PAD (bsink)); + + /* make sure we have caps on the pad */ + if (!padcaps && !bufcaps) + goto no_caps; +#endif + + /* get IN_CAPS first, code below might mess with the flags */ + in_caps = GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_IN_CAPS); + +#if 0 + /* stamp the buffer with previous caps if no caps set */ + if (!bufcaps) { + if (!gst_buffer_is_writable (buf)) { + /* metadata is not writable, copy will be made and original buffer + * will be unreffed so we need to ref so that we don't lose the + * buffer in the render method. */ + gst_buffer_ref (buf); + /* the new buffer is ours only, we keep it out of the scope of this + * function */ + buf = gst_buffer_make_writable (buf); + } else { + /* else the metadata is writable, we ref because we keep the buffer + * out of the scope of this method */ + gst_buffer_ref (buf); + } + /* buffer metadata is writable now, set the caps */ + gst_buffer_set_caps (buf, padcaps); + } else { + gst_caps_unref (bufcaps); + + /* since we keep this buffer out of the scope of this method */ + gst_buffer_ref (buf); + } +#endif + + GST_LOG_OBJECT (sink, "received buffer %p, in_caps: %s, offset %" + G_GINT64_FORMAT ", offset_end %" G_GINT64_FORMAT + ", timestamp %" GST_TIME_FORMAT ", duration %" GST_TIME_FORMAT, + buf, in_caps ? "yes" : "no", GST_BUFFER_OFFSET (buf), + GST_BUFFER_OFFSET_END (buf), + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)), + GST_TIME_ARGS (GST_BUFFER_DURATION (buf))); + + /* if we get IN_CAPS buffers, but the previous buffer was not IN_CAPS, + * it means we're getting new streamheader buffers, and we should clear + * the old ones */ + if (in_caps && sink->previous_buffer_in_caps == FALSE) { + GST_DEBUG_OBJECT (sink, + "receiving new IN_CAPS buffers, clearing old streamheader"); + g_slist_foreach (sink->streamheader, (GFunc) gst_mini_object_unref, NULL); + g_slist_free (sink->streamheader); + sink->streamheader = NULL; + } + + /* save the current in_caps */ + sink->previous_buffer_in_caps = in_caps; + + /* if the incoming buffer is marked as IN CAPS, then we assume for now + * it's a streamheader that needs to be sent to each new client, so we + * put it on our internal list of streamheader buffers. + * FIXME: we could check if the buffer's contents are in fact part of the + * current streamheader. + * + * We don't send the buffer to the client, since streamheaders are sent + * separately when necessary. */ + if (in_caps) { + GST_DEBUG_OBJECT (sink, "appending IN_CAPS buffer with length %" + G_GSIZE_FORMAT " to streamheader", gst_buffer_get_size (buf)); + sink->streamheader = g_slist_append (sink->streamheader, buf); + } else { + /* queue the buffer, this is a regular data buffer. */ + this->class->queue_buffer (sink, buf); + + sink->bytes_to_serve += gst_buffer_get_size (buf); + } + return GST_FLOW_OK; + + /* ERRORS */ +#if 0 +no_caps: + { + GST_ELEMENT_ERROR (sink, CORE, NEGOTIATION, (NULL), + ("Received first buffer without caps set")); + return GST_FLOW_NOT_NEGOTIATED; + } +#endif +} + +static void +gst_multi_output_sink_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstMultiOutputSink *multioutputsink; + + multioutputsink = GST_MULTI_OUTPUT_SINK (object); + + switch (prop_id) { + case PROP_BUFFERS_MAX: + multioutputsink->units_max = g_value_get_int (value); + break; + case PROP_BUFFERS_SOFT_MAX: + multioutputsink->units_soft_max = g_value_get_int (value); + break; + case PROP_TIME_MIN: + multioutputsink->time_min = g_value_get_int64 (value); + break; + case PROP_BYTES_MIN: + multioutputsink->bytes_min = g_value_get_int (value); + break; + case PROP_BUFFERS_MIN: + multioutputsink->buffers_min = g_value_get_int (value); + break; + case PROP_UNIT_TYPE: + multioutputsink->unit_type = g_value_get_enum (value); + break; + case PROP_UNITS_MAX: + multioutputsink->units_max = g_value_get_int64 (value); + break; + case PROP_UNITS_SOFT_MAX: + multioutputsink->units_soft_max = g_value_get_int64 (value); + break; + case PROP_RECOVER_POLICY: + multioutputsink->recover_policy = g_value_get_enum (value); + break; + case PROP_TIMEOUT: + multioutputsink->timeout = g_value_get_uint64 (value); + break; + case PROP_SYNC_METHOD: + multioutputsink->def_sync_method = g_value_get_enum (value); + break; + case PROP_BURST_FORMAT: + multioutputsink->def_burst_format = g_value_get_enum (value); + break; + case PROP_BURST_VALUE: + multioutputsink->def_burst_value = g_value_get_uint64 (value); + break; + case PROP_QOS_DSCP: + multioutputsink->qos_dscp = g_value_get_int (value); + setup_dscp (multioutputsink); + break; + case PROP_HANDLE_READ: + multioutputsink->handle_read = g_value_get_boolean (value); + break; + case PROP_RESEND_STREAMHEADER: + multioutputsink->resend_streamheader = g_value_get_boolean (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_multi_output_sink_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstMultiOutputSink *multioutputsink; + + multioutputsink = GST_MULTI_OUTPUT_SINK (object); + + switch (prop_id) { + case PROP_BUFFERS_MAX: + g_value_set_int (value, multioutputsink->units_max); + break; + case PROP_BUFFERS_SOFT_MAX: + g_value_set_int (value, multioutputsink->units_soft_max); + break; + case PROP_TIME_MIN: + g_value_set_int64 (value, multioutputsink->time_min); + break; + case PROP_BYTES_MIN: + g_value_set_int (value, multioutputsink->bytes_min); + break; + case PROP_BUFFERS_MIN: + g_value_set_int (value, multioutputsink->buffers_min); + break; + case PROP_BUFFERS_QUEUED: + g_value_set_uint (value, multioutputsink->buffers_queued); + break; + case PROP_BYTES_QUEUED: + g_value_set_uint (value, multioutputsink->bytes_queued); + break; + case PROP_TIME_QUEUED: + g_value_set_uint64 (value, multioutputsink->time_queued); + break; + case PROP_UNIT_TYPE: + g_value_set_enum (value, multioutputsink->unit_type); + break; + case PROP_UNITS_MAX: + g_value_set_int64 (value, multioutputsink->units_max); + break; + case PROP_UNITS_SOFT_MAX: + g_value_set_int64 (value, multioutputsink->units_soft_max); + break; + case PROP_RECOVER_POLICY: + g_value_set_enum (value, multioutputsink->recover_policy); + break; + case PROP_TIMEOUT: + g_value_set_uint64 (value, multioutputsink->timeout); + break; + case PROP_SYNC_METHOD: + g_value_set_enum (value, multioutputsink->def_sync_method); + break; + case PROP_BYTES_TO_SERVE: + g_value_set_uint64 (value, multioutputsink->bytes_to_serve); + break; + case PROP_BYTES_SERVED: + g_value_set_uint64 (value, multioutputsink->bytes_served); + break; + case PROP_BURST_FORMAT: + g_value_set_enum (value, multioutputsink->def_burst_format); + break; + case PROP_BURST_VALUE: + g_value_set_uint64 (value, multioutputsink->def_burst_value); + break; + case PROP_QOS_DSCP: + g_value_set_int (value, multioutputsink->qos_dscp); + break; + case PROP_HANDLE_READ: + g_value_set_boolean (value, multioutputsink->handle_read); + break; + case PROP_RESEND_STREAMHEADER: + g_value_set_boolean (value, multioutputsink->resend_streamheader); + break; + case PROP_NUM_OUTPUTS: + g_value_set_uint (value, + g_hash_table_size (multioutputsink->output_hash)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + + +/* create a output for sending to remote machine */ +static gboolean +gst_multi_output_sink_start (GstBaseSink * bsink) +{ + GstMultiOutputSinkClass *fclass; + GstMultiOutputSink *this; + GList *clients; + + if (GST_OBJECT_FLAG_IS_SET (bsink, GST_MULTI_OUTPUT_SINK_OPEN)) + return TRUE; + + this = GST_MULTI_OUTPUT_SINK (bsink); + fclass = GST_MULTI_OUTPUT_SINK_GET_CLASS (this); + + GST_INFO_OBJECT (this, "starting"); + /* FIXME: until here the same */ + +#if 0 + this->main_context = g_main_context_new (); + + CLIENTS_LOCK (this); + for (clients = this->clients; clients; clients = clients->next) { + GstOutputClient *client; + + client = clients->data; + if (client->source) + continue; + client->source = + g_output_create_source (client->output, + G_IO_IN | G_IO_OUT | G_IO_PRI | G_IO_ERR | G_IO_HUP, this->cancellable); + g_source_set_callback (client->source, + (GSourceFunc) gst_multi_output_sink_output_condition, + gst_object_ref (this), (GDestroyNotify) gst_object_unref); + g_source_attach (client->source, this->main_context); + } + CLIENTS_UNLOCK (this); + + /* FIXME: this bit shared */ +#endif + this->streamheader = NULL; + this->bytes_to_serve = 0; + this->bytes_served = 0; + + if (fclass->init) { + fclass->init (this); + } + + this->running = TRUE; + + this->thread = g_thread_new ("multioutputsink", + (GThreadFunc) gst_multi_output_sink_thread, this); + + GST_OBJECT_FLAG_SET (this, GST_MULTI_OUTPUT_SINK_OPEN); + + return TRUE; +} + +static gboolean +multioutputsink_hash_remove (gpointer key, gpointer value, gpointer data) +{ + return TRUE; +} + +static gboolean +gst_multi_output_sink_stop (GstBaseSink * bsink) +{ + GstMultiOutputSinkClass *fclass; + GstMultiOutputSink *this; + GstBuffer *buf; + gint i; + + this = GST_MULTI_OUTPUT_SINK (bsink); + fclass = GST_MULTI_OUTPUT_SINK_GET_CLASS (this); + + if (!GST_OBJECT_FLAG_IS_SET (bsink, GST_MULTI_OUTPUT_SINK_OPEN)) + return TRUE; + + this->running = FALSE; + + if (parent_class->wakeup) + parent_class->wakeup (this); + + + if (this->thread) { + GST_DEBUG_OBJECT (this, "joining thread"); + g_thread_join (this->thread); + GST_DEBUG_OBJECT (this, "joined thread"); + this->thread = NULL; + } + + /* free the clients */ + gst_multi_output_sink_clear (this); + + if (this->streamheader) { + g_slist_foreach (this->streamheader, (GFunc) gst_mini_object_unref, NULL); + g_slist_free (this->streamheader); + this->streamheader = NULL; + } + + if (fclass->close) + fclass->close (this); + +#ifdef 0 + if (this->main_context) { + g_main_context_unref (this->main_context); + this->main_context = NULL; + } +#endif + + g_hash_table_foreach_remove (this->output_hash, multioutputsink_hash_remove, + this); + + /* remove all queued buffers */ + if (this->bufqueue) { + GST_DEBUG_OBJECT (this, "Emptying bufqueue with %d buffers", + this->bufqueue->len); + for (i = this->bufqueue->len - 1; i >= 0; --i) { + buf = g_array_index (this->bufqueue, GstBuffer *, i); + GST_LOG_OBJECT (this, "Removing buffer %p (%d) with refcount %d", buf, i, + GST_MINI_OBJECT_REFCOUNT (buf)); + gst_buffer_unref (buf); + this->bufqueue = g_array_remove_index (this->bufqueue, i); + } + /* freeing the array is done in _finalize */ + } + GST_OBJECT_FLAG_UNSET (this, GST_MULTI_OUTPUT_SINK_OPEN); + + return TRUE; +} + +static GstStateChangeReturn +gst_multi_output_sink_change_state (GstElement * element, + GstStateChange transition) +{ + GstMultiOutputSink *sink; + GstStateChangeReturn ret; + + sink = GST_MULTI_OUTPUT_SINK (element); + + /* we disallow changing the state from the streaming thread */ + if (g_thread_self () == sink->thread) + return GST_STATE_CHANGE_FAILURE; + + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + if (!parent_class->start (GST_BASE_SINK (sink))) + goto start_failed; + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + break; + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + break; + case GST_STATE_CHANGE_READY_TO_NULL: + gst_multi_output_sink_stop (GST_BASE_SINK (sink)); + break; + default: + break; + } + return ret; + + /* ERRORS */ +start_failed: + { + /* error message was posted */ + return GST_STATE_CHANGE_FAILURE; + } +} + +#if 0 +static gboolean +gst_multi_output_sink_unlock (GstBaseSink * bsink) +{ + GstMultiOutputSink *sink; + + sink = GST_MULTI_OUTPUT_SINK (bsink); + + GST_DEBUG_OBJECT (sink, "set to flushing"); + g_cancellable_cancel (sink->cancellable); + if (sink->main_context) + g_main_context_wakeup (sink->main_context); + + return TRUE; +} + +/* will be called only between calls to start() and stop() */ +static gboolean +gst_multi_output_sink_unlock_stop (GstBaseSink * bsink) +{ + GstMultiOutputSink *sink; + + sink = GST_MULTI_OUTPUT_SINK (bsink); + + GST_DEBUG_OBJECT (sink, "unset flushing"); + g_cancellable_reset (sink->cancellable); + + return TRUE; +} +#endif diff --git a/gst/tcp/gstmultisocketsink.c b/gst/tcp/gstmultisocketsink.c index 44f62c6..503abe2 100644 --- a/gst/tcp/gstmultisocketsink.c +++ b/gst/tcp/gstmultisocketsink.c @@ -154,17 +154,11 @@ enum #define DEFAULT_BURST_VALUE 0 #define DEFAULT_QOS_DSCP -1 -#define DEFAULT_HANDLE_READ TRUE - -#define DEFAULT_RESEND_STREAMHEADER TRUE enum { PROP_0, PROP_MODE, - PROP_BUFFERS_QUEUED, - PROP_BYTES_QUEUED, - PROP_TIME_QUEUED, PROP_UNIT_TYPE, PROP_UNITS_MAX, @@ -178,19 +172,19 @@ enum PROP_QOS_DSCP, - PROP_HANDLE_READ, - - PROP_RESEND_STREAMHEADER, - PROP_NUM_SOCKETS, PROP_LAST }; - static void gst_multi_socket_sink_finalize (GObject * object); -static void gst_multi_socket_sink_remove_client_link (GstMultiSocketSink * sink, +static void gst_multi_socket_sink_stop_pre (GstMultiHandleSink * mhsink); +static void gst_multi_socket_sink_stop_post (GstMultiHandleSink * mhsink); +static gboolean gst_multi_socket_sink_start_pre (GstMultiHandleSink * mhsink); +static gpointer gst_multi_socket_sink_thread (GstMultiHandleSink * mhsink); + +static void gst_multi_socket_sink_remove_client_link (GstMultiHandleSink * sink, GList * link); static gboolean gst_multi_socket_sink_socket_condition (GSocket * socket, GIOCondition condition, GstMultiSocketSink * sink); @@ -199,8 +193,6 @@ static GstFlowReturn gst_multi_socket_sink_render (GstBaseSink * bsink, GstBuffer * buf); static gboolean gst_multi_socket_sink_unlock (GstBaseSink * bsink); static gboolean gst_multi_socket_sink_unlock_stop (GstBaseSink * bsink); -static GstStateChangeReturn gst_multi_socket_sink_change_state (GstElement * - element, GstStateChange transition); static void gst_multi_socket_sink_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); @@ -256,21 +248,6 @@ gst_multi_socket_sink_class_init (GstMultiSocketSinkClass * klass) G_MAXINT64, DEFAULT_UNITS_SOFT_MAX, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - g_object_class_install_property (gobject_class, PROP_BUFFERS_QUEUED, - g_param_spec_uint ("buffers-queued", "Buffers queued", - "Number of buffers currently queued", 0, G_MAXUINT, 0, - G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); -#if NOT_IMPLEMENTED - g_object_class_install_property (gobject_class, PROP_BYTES_QUEUED, - g_param_spec_uint ("bytes-queued", "Bytes queued", - "Number of bytes currently queued", 0, G_MAXUINT, 0, - G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); - g_object_class_install_property (gobject_class, PROP_TIME_QUEUED, - g_param_spec_uint64 ("time-queued", "Time queued", - "Number of time currently queued", 0, G_MAXUINT64, 0, - G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); -#endif - g_object_class_install_property (gobject_class, PROP_BURST_FORMAT, g_param_spec_enum ("burst-format", "Burst format", "The format of the burst units (when sync-method is burst[[-with]-keyframe])", @@ -287,30 +264,6 @@ gst_multi_socket_sink_class_init (GstMultiSocketSinkClass * klass) -1, 63, DEFAULT_QOS_DSCP, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - /** - * GstMultiSocketSink::handle-read - * - * Handle read requests from clients and discard the data. - * - * Since: 0.10.23 - */ - g_object_class_install_property (gobject_class, PROP_HANDLE_READ, - g_param_spec_boolean ("handle-read", "Handle Read", - "Handle client reads and discard the data", - DEFAULT_HANDLE_READ, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - /** - * GstMultiSocketSink::resend-streamheader - * - * Resend the streamheaders to existing clients when they change. - * - * Since: 0.10.23 - */ - g_object_class_install_property (gobject_class, PROP_RESEND_STREAMHEADER, - g_param_spec_boolean ("resend-streamheader", "Resend streamheader", - "Resend the streamheader if it changes in the caps", - DEFAULT_RESEND_STREAMHEADER, - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - g_object_class_install_property (gobject_class, PROP_NUM_SOCKETS, g_param_spec_uint ("num-sockets", "Number of sockets", "The current number of client sockets", @@ -460,16 +413,23 @@ gst_multi_socket_sink_class_init (GstMultiSocketSinkClass * klass) "Wim Taymans , " "Sebastian Dröge "); - gstelement_class->change_state = - GST_DEBUG_FUNCPTR (gst_multi_socket_sink_change_state); - gstbasesink_class->render = GST_DEBUG_FUNCPTR (gst_multi_socket_sink_render); + gstbasesink_class->unlock = GST_DEBUG_FUNCPTR (gst_multi_socket_sink_unlock); gstbasesink_class->unlock_stop = GST_DEBUG_FUNCPTR (gst_multi_socket_sink_unlock_stop); - gstmultihandlesink_class->clear = - GST_DEBUG_FUNCPTR (gst_multi_socket_sink_clear); + gstmultihandlesink_class->stop_pre = + GST_DEBUG_FUNCPTR (gst_multi_socket_sink_stop_pre); + gstmultihandlesink_class->stop_post = + GST_DEBUG_FUNCPTR (gst_multi_socket_sink_stop_post); + gstmultihandlesink_class->start_pre = + GST_DEBUG_FUNCPTR (gst_multi_socket_sink_start_pre); + gstmultihandlesink_class->thread = + GST_DEBUG_FUNCPTR (gst_multi_socket_sink_thread); + + gstmultihandlesink_class->remove_client_link = + GST_DEBUG_FUNCPTR (gst_multi_socket_sink_remove_client_link); klass->add = GST_DEBUG_FUNCPTR (gst_multi_socket_sink_add); klass->add_full = GST_DEBUG_FUNCPTR (gst_multi_socket_sink_add_full); @@ -484,13 +444,8 @@ gst_multi_socket_sink_class_init (GstMultiSocketSinkClass * klass) static void gst_multi_socket_sink_init (GstMultiSocketSink * this) { - GST_OBJECT_FLAG_UNSET (this, GST_MULTI_HANDLE_SINK_OPEN); - - CLIENTS_LOCK_INIT (this); - this->clients = NULL; this->socket_hash = g_hash_table_new (g_direct_hash, g_int_equal); - this->bufqueue = g_array_new (FALSE, TRUE, sizeof (GstBuffer *)); this->unit_type = DEFAULT_UNIT_TYPE; this->units_max = DEFAULT_UNITS_MAX; this->units_soft_max = DEFAULT_UNITS_SOFT_MAX; @@ -499,9 +454,6 @@ gst_multi_socket_sink_init (GstMultiSocketSink * this) this->def_burst_value = DEFAULT_BURST_VALUE; this->qos_dscp = DEFAULT_QOS_DSCP; - this->handle_read = DEFAULT_HANDLE_READ; - - this->resend_streamheader = DEFAULT_RESEND_STREAMHEADER; this->header_flags = 0; this->cancellable = g_cancellable_new (); @@ -510,14 +462,9 @@ gst_multi_socket_sink_init (GstMultiSocketSink * this) static void gst_multi_socket_sink_finalize (GObject * object) { - GstMultiSocketSink *this; - - this = GST_MULTI_SOCKET_SINK (object); + GstMultiSocketSink *this = GST_MULTI_SOCKET_SINK (object); - CLIENTS_LOCK_CLEAR (this); g_hash_table_destroy (this->socket_hash); - g_array_free (this->bufqueue, TRUE); - if (this->cancellable) { g_object_unref (this->cancellable); this->cancellable = NULL; @@ -595,9 +542,10 @@ static void setup_dscp (GstMultiSocketSink * sink) { GList *clients; + GstMultiHandleSink *mhsink = GST_MULTI_HANDLE_SINK (sink); CLIENTS_LOCK (sink); - for (clients = sink->clients; clients; clients = clients->next) { + for (clients = mhsink->clients; clients; clients = clients->next) { GstSocketClient *client; client = clients->data; @@ -616,6 +564,7 @@ gst_multi_socket_sink_add_full (GstMultiSocketSink * sink, GSocket * socket, GstSocketClient *client; GstMultiHandleClient *mhclient; GList *clink; + GstMultiHandleSink *mhsink = GST_MULTI_HANDLE_SINK (sink); GST_DEBUG_OBJECT (sink, "[socket %p] adding client, sync_method %d, " "min_format %d, min_value %" G_GUINT64_FORMAT @@ -632,6 +581,7 @@ gst_multi_socket_sink_add_full (GstMultiSocketSink * sink, GSocket * socket, client = g_new0 (GstSocketClient, 1); mhclient = (GstMultiHandleClient *) client; gst_multi_handle_sink_client_init (mhclient, sync_method); + g_snprintf (mhclient->debug, 30, "[socket %p]", socket); client->socket = G_SOCKET (g_object_ref (socket)); client->burst_min_format = min_format; @@ -647,9 +597,9 @@ gst_multi_socket_sink_add_full (GstMultiSocketSink * sink, GSocket * socket, goto duplicate; /* we can add the fd now */ - clink = sink->clients = g_list_prepend (sink->clients, client); + clink = mhsink->clients = g_list_prepend (mhsink->clients, client); g_hash_table_insert (sink->socket_hash, socket, clink); - sink->clients_cookie++; + mhsink->clients_cookie++; /* set the socket to non blocking */ g_socket_set_blocking (socket, FALSE); @@ -714,6 +664,9 @@ void gst_multi_socket_sink_remove (GstMultiSocketSink * sink, GSocket * socket) { GList *clink; + GstMultiHandleSink *mhsink = GST_MULTI_HANDLE_SINK (sink); + GstMultiHandleSinkClass *mhsinkclass = + GST_MULTI_HANDLE_SINK_GET_CLASS (mhsink); GST_DEBUG_OBJECT (sink, "[socket %p] removing client", socket); @@ -731,7 +684,7 @@ gst_multi_socket_sink_remove (GstMultiSocketSink * sink, GSocket * socket) } mhclient->status = GST_CLIENT_STATUS_REMOVED; - gst_multi_socket_sink_remove_client_link (sink, clink); + mhsinkclass->remove_client_link (GST_MULTI_HANDLE_SINK (sink), clink); } else { GST_WARNING_OBJECT (sink, "[socket %p] no client with this socket found!", socket); @@ -777,40 +730,6 @@ done: CLIENTS_UNLOCK (sink); } -/* can be called both through the signal (i.e. from any thread) or when - * stopping, after the writing thread has shut down */ -void -gst_multi_socket_sink_clear (GstMultiHandleSink * mhsink) -{ - GList *clients, *next; - guint32 cookie; - GstMultiSocketSink *sink = GST_MULTI_SOCKET_SINK (mhsink); - - GST_DEBUG_OBJECT (sink, "clearing all clients"); - - CLIENTS_LOCK (sink); -restart: - cookie = sink->clients_cookie; - for (clients = sink->clients; clients; clients = next) { - GstMultiHandleClient *mhclient; - - if (cookie != sink->clients_cookie) { - GST_DEBUG_OBJECT (sink, "cookie changed while removing all clients"); - goto restart; - } - - mhclient = (GstMultiHandleClient *) clients->data; - next = g_list_next (clients); - - mhclient->status = GST_CLIENT_STATUS_REMOVED; - /* the next call changes the list, which is why we iterate - * with a temporary next pointer */ - gst_multi_socket_sink_remove_client_link (sink, clients); - } - - CLIENTS_UNLOCK (sink); -} - /* "get-stats" signal implementation */ GstStructure * @@ -871,13 +790,14 @@ noclient: * close the fd itself. */ static void -gst_multi_socket_sink_remove_client_link (GstMultiSocketSink * sink, +gst_multi_socket_sink_remove_client_link (GstMultiHandleSink * sink, GList * link) { GSocket *socket; GTimeVal now; GstSocketClient *client = link->data; GstMultiHandleClient *mhclient = (GstMultiHandleClient *) client; + GstMultiSocketSink *mssink = GST_MULTI_SOCKET_SINK (sink); GstMultiSocketSinkClass *fclass; fclass = GST_MULTI_SOCKET_SINK_GET_CLASS (sink); @@ -885,8 +805,8 @@ gst_multi_socket_sink_remove_client_link (GstMultiSocketSink * sink, socket = client->socket; if (mhclient->currently_removing) { - GST_WARNING_OBJECT (sink, "[socket %p] client is already being removed", - socket); + GST_WARNING_OBJECT (sink, "%s client is already being removed", + mhclient->debug); return; } else { mhclient->currently_removing = TRUE; @@ -895,31 +815,31 @@ gst_multi_socket_sink_remove_client_link (GstMultiSocketSink * sink, /* FIXME: if we keep track of ip we can log it here and signal */ switch (mhclient->status) { case GST_CLIENT_STATUS_OK: - GST_WARNING_OBJECT (sink, "[socket %p] removing client %p for no reason", - socket, client); + GST_WARNING_OBJECT (sink, "%s removing client %p for no reason", + mhclient->debug, client); break; case GST_CLIENT_STATUS_CLOSED: - GST_DEBUG_OBJECT (sink, "[socket %p] removing client %p because of close", - socket, client); + GST_DEBUG_OBJECT (sink, "%s removing client %p because of close", + mhclient->debug, client); break; case GST_CLIENT_STATUS_REMOVED: GST_DEBUG_OBJECT (sink, - "[socket %p] removing client %p because the app removed it", socket, + "%s removing client %p because the app removed it", mhclient->debug, client); break; case GST_CLIENT_STATUS_SLOW: GST_INFO_OBJECT (sink, - "[socket %p] removing client %p because it was too slow", socket, + "%s removing client %p because it was too slow", mhclient->debug, client); break; case GST_CLIENT_STATUS_ERROR: GST_WARNING_OBJECT (sink, - "[socket %p] removing client %p because of error", socket, client); + "%s removing client %p because of error", mhclient->debug, client); break; case GST_CLIENT_STATUS_FLUSHING: default: GST_WARNING_OBJECT (sink, - "[socket %p] removing client %p with invalid reason %d", socket, + "%s removing client %p with invalid reason %d", mhclient->debug, client, mhclient->status); break; } @@ -955,7 +875,7 @@ gst_multi_socket_sink_remove_client_link (GstMultiSocketSink * sink, /* fd cannot be reused in the above signal callback so we can safely * remove it from the hashtable here */ - if (!g_hash_table_remove (sink->socket_hash, socket)) { + if (!g_hash_table_remove (mssink->socket_hash, socket)) { GST_WARNING_OBJECT (sink, "[socket %p] error removing client %p from hash", socket, client); } @@ -968,7 +888,7 @@ gst_multi_socket_sink_remove_client_link (GstMultiSocketSink * sink, sink->clients_cookie++; if (fclass->removed) - fclass->removed (sink, socket); + fclass->removed (mssink, socket); g_free (client); CLIENTS_UNLOCK (sink); @@ -1036,24 +956,13 @@ gst_multi_socket_sink_handle_client_read (GstMultiSocketSink * sink, return ret; } -static gboolean -is_sync_frame (GstMultiSocketSink * sink, GstBuffer * buffer) -{ - if (GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT)) { - return FALSE; - } else if (!GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_HEADER)) { - return TRUE; - } - - return FALSE; -} - /* queue the given buffer for the given client */ static gboolean gst_multi_socket_sink_client_queue_buffer (GstMultiSocketSink * sink, GstSocketClient * client, GstBuffer * buffer) { GstMultiHandleClient *mhclient = (GstMultiHandleClient *) client; + GstMultiHandleSink *mhsink = GST_MULTI_HANDLE_SINK (sink); GstCaps *caps; /* TRUE: send them if the new caps have them */ @@ -1093,7 +1002,7 @@ gst_multi_socket_sink_client_queue_buffer (GstMultiSocketSink * sink, send_streamheader = TRUE; } else { /* both old and new caps have streamheader set */ - if (!sink->resend_streamheader) { + if (!mhsink->resend_streamheader) { GST_DEBUG_OBJECT (sink, "[socket %p] asked to not resend the streamheader, not sending", client->socket); @@ -1168,40 +1077,6 @@ gst_multi_socket_sink_client_queue_buffer (GstMultiSocketSink * sink, return TRUE; } -/* find the keyframe in the list of buffers starting the - * search from @idx. @direction as -1 will search backwards, - * 1 will search forwards. - * Returns: the index or -1 if there is no keyframe after idx. - */ -static gint -find_syncframe (GstMultiSocketSink * sink, gint idx, gint direction) -{ - gint i, len, result; - - /* take length of queued buffers */ - len = sink->bufqueue->len; - - /* assume we don't find a keyframe */ - result = -1; - - /* then loop over all buffers to find the first keyframe */ - for (i = idx; i >= 0 && i < len; i += direction) { - GstBuffer *buf; - - buf = g_array_index (sink->bufqueue, GstBuffer *, i); - if (is_sync_frame (sink, buf)) { - GST_LOG_OBJECT (sink, "found keyframe at %d from %d, direction %d", - i, idx, direction); - result = i; - break; - } - } - return result; -} - -#define find_next_syncframe(s,i) find_syncframe(s,i,1) -#define find_prev_syncframe(s,i) find_syncframe(s,i,-1) - /* Get the number of buffers from the buffer queue needed to satisfy * the maximum max in the configured units. * If units are not BUFFERS, and there are insufficient buffers in the @@ -1209,6 +1084,8 @@ find_syncframe (GstMultiSocketSink * sink, gint idx, gint direction) static gint get_buffers_max (GstMultiSocketSink * sink, gint64 max) { + GstMultiHandleSink *mhsink = GST_MULTI_HANDLE_SINK (sink); + switch (sink->unit_type) { case GST_FORMAT_BUFFERS: return max; @@ -1220,10 +1097,10 @@ get_buffers_max (GstMultiSocketSink * sink, gint64 max) gint64 diff; GstClockTime first = GST_CLOCK_TIME_NONE; - len = sink->bufqueue->len; + len = mhsink->bufqueue->len; for (i = 0; i < len; i++) { - buf = g_array_index (sink->bufqueue, GstBuffer *, i); + buf = g_array_index (mhsink->bufqueue, GstBuffer *, i); if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) { if (first == -1) first = GST_BUFFER_TIMESTAMP (buf); @@ -1243,10 +1120,10 @@ get_buffers_max (GstMultiSocketSink * sink, gint64 max) int len; gint acc = 0; - len = sink->bufqueue->len; + len = mhsink->bufqueue->len; for (i = 0; i < len; i++) { - buf = g_array_index (sink->bufqueue, GstBuffer *, i); + buf = g_array_index (mhsink->bufqueue, GstBuffer *, i); acc += gst_buffer_get_size (buf); if (acc > max) @@ -1279,9 +1156,10 @@ find_limits (GstMultiSocketSink * sink, GstClockTime first, time; gint i, len, bytes; gboolean result, max_hit; + GstMultiHandleSink *mhsink = GST_MULTI_HANDLE_SINK (sink); /* take length of queue */ - len = sink->bufqueue->len; + len = mhsink->bufqueue->len; /* this must hold */ g_assert (len > 0); @@ -1328,7 +1206,7 @@ find_limits (GstMultiSocketSink * sink, result = *min_idx != -1; break; } - buf = g_array_index (sink->bufqueue, GstBuffer *, i); + buf = g_array_index (mhsink->bufqueue, GstBuffer *, i); bytes += gst_buffer_get_size (buf); @@ -1441,12 +1319,13 @@ gst_multi_socket_sink_new_client (GstMultiSocketSink * sink, { gint result; GstMultiHandleClient *mhclient = (GstMultiHandleClient *) client; + GstMultiHandleSink *mhsink = GST_MULTI_HANDLE_SINK (sink); GST_DEBUG_OBJECT (sink, "[socket %p] new client, deciding where to start in queue", client->socket); GST_DEBUG_OBJECT (sink, "queue is currently %d buffers long", - sink->bufqueue->len); + mhsink->bufqueue->len); switch (mhclient->sync_method) { case GST_SYNC_METHOD_LATEST: /* no syncing, we are happy with whatever the client is going to get */ @@ -1463,7 +1342,7 @@ gst_multi_socket_sink_new_client (GstMultiSocketSink * sink, "[socket %p] new client, bufpos %d, waiting for keyframe", client->socket, mhclient->bufpos); - result = find_prev_syncframe (sink, mhclient->bufpos); + result = find_prev_syncframe (mhsink, mhclient->bufpos); if (result != -1) { GST_DEBUG_OBJECT (sink, "[socket %p] SYNC_METHOD_NEXT_KEYFRAME: result %d", @@ -1489,7 +1368,7 @@ gst_multi_socket_sink_new_client (GstMultiSocketSink * sink, * we need to wait for the next keyframe and so we change the client's * sync method to GST_SYNC_METHOD_NEXT_KEYFRAME. */ - result = find_next_syncframe (sink, 0); + result = find_next_syncframe (mhsink, 0); if (result != -1) { GST_DEBUG_OBJECT (sink, "[socket %p] SYNC_METHOD_LATEST_KEYFRAME: result %d", @@ -1554,7 +1433,7 @@ gst_multi_socket_sink_new_client (GstMultiSocketSink * sink, GST_LOG_OBJECT (sink, "min %d, max %d", min_idx, max_idx); /* first find a keyframe after min_idx */ - next_syncframe = find_next_syncframe (sink, min_idx); + next_syncframe = find_next_syncframe (mhsink, min_idx); if (next_syncframe != -1 && next_syncframe < max_idx) { /* we have a valid keyframe and it's below the max */ GST_LOG_OBJECT (sink, "found keyframe in min/max limits"); @@ -1563,7 +1442,7 @@ gst_multi_socket_sink_new_client (GstMultiSocketSink * sink, } /* no valid keyframe, try to find one below min */ - prev_syncframe = find_prev_syncframe (sink, min_idx); + prev_syncframe = find_prev_syncframe (mhsink, min_idx); if (prev_syncframe != -1) { GST_WARNING_OBJECT (sink, "using keyframe below min in BURST_KEYFRAME sync mode"); @@ -1601,7 +1480,7 @@ gst_multi_socket_sink_new_client (GstMultiSocketSink * sink, GST_LOG_OBJECT (sink, "min %d, max %d", min_idx, max_idx); /* first find a keyframe after min_idx */ - next_syncframe = find_next_syncframe (sink, min_idx); + next_syncframe = find_next_syncframe (mhsink, min_idx); if (next_syncframe != -1 && next_syncframe < max_idx) { /* we have a valid keyframe and it's below the max */ GST_LOG_OBJECT (sink, "found keyframe in min/max limits"); @@ -1720,7 +1599,7 @@ gst_multi_socket_sink_handle_client_write (GstMultiSocketSink * sink, goto flushed; /* grab buffer */ - buf = g_array_index (sink->bufqueue, GstBuffer *, mhclient->bufpos); + buf = g_array_index (mhsink->bufqueue, GstBuffer *, mhclient->bufpos); mhclient->bufpos--; /* update stats */ @@ -1856,14 +1735,14 @@ gst_multi_socket_sink_recover_client (GstMultiSocketSink * sink, case GST_RECOVER_POLICY_RESYNC_KEYFRAME: /* find keyframe in buffers, we search backwards to find the * closest keyframe relative to what this client already received. */ - newbufpos = MIN (sink->bufqueue->len - 1, + newbufpos = MIN (mhsink->bufqueue->len - 1, get_buffers_max (sink, sink->units_soft_max) - 1); while (newbufpos >= 0) { GstBuffer *buf; - buf = g_array_index (sink->bufqueue, GstBuffer *, newbufpos); - if (is_sync_frame (sink, buf)) { + buf = g_array_index (mhsink->bufqueue, GstBuffer *, newbufpos); + if (is_sync_frame (mhsink, buf)) { /* found a buffer that is not a delta unit */ break; } @@ -1907,9 +1786,9 @@ gst_multi_socket_sink_queue_buffer (GstMultiSocketSink * sink, GstBuffer * buf) GstClockTime now; gint max_buffers, soft_max_buffers; guint cookie; - GstMultiHandleSink *mhsink; - - mhsink = GST_MULTI_HANDLE_SINK (sink); + GstMultiHandleSink *mhsink = GST_MULTI_HANDLE_SINK (sink); + GstMultiHandleSinkClass *mhsinkclass = + GST_MULTI_HANDLE_SINK_GET_CLASS (mhsink); g_get_current_time (&nowtv); now = GST_TIMEVAL_TO_TIME (nowtv); @@ -1917,8 +1796,8 @@ gst_multi_socket_sink_queue_buffer (GstMultiSocketSink * sink, GstBuffer * buf) CLIENTS_LOCK (sink); /* add buffer to queue */ gst_buffer_ref (buf); - g_array_prepend_val (sink->bufqueue, buf); - queuelen = sink->bufqueue->len; + g_array_prepend_val (mhsink->bufqueue, buf); + queuelen = mhsink->bufqueue->len; if (sink->units_max > 0) max_buffers = get_buffers_max (sink, sink->units_max); @@ -1936,12 +1815,12 @@ gst_multi_socket_sink_queue_buffer (GstMultiSocketSink * sink, GstBuffer * buf) max_buffer_usage = 0; restart: - cookie = sink->clients_cookie; - for (clients = sink->clients; clients; clients = next) { + cookie = mhsink->clients_cookie; + for (clients = mhsink->clients; clients; clients = next) { GstSocketClient *client; GstMultiHandleClient *mhclient; - if (cookie != sink->clients_cookie) { + if (cookie != mhsink->clients_cookie) { GST_DEBUG_OBJECT (sink, "Clients cookie outdated, restarting"); goto restart; } @@ -1982,7 +1861,7 @@ restart: mhclient->status = GST_CLIENT_STATUS_SLOW; /* set client to invalid position while being removed */ mhclient->bufpos = -1; - gst_multi_socket_sink_remove_client_link (sink, clients); + mhsinkclass->remove_client_link (mhsink, clients); continue; } else if (mhclient->bufpos == 0 || mhclient->new_connection) { /* can send data to this client now. need to signal the select thread that @@ -2041,8 +1920,8 @@ restart: "extending queue to include sync point, now at %d, limit is %d", max_buffer_usage, limit); for (i = 0; i < limit; i++) { - buf = g_array_index (sink->bufqueue, GstBuffer *, i); - if (is_sync_frame (sink, buf)) { + buf = g_array_index (mhsink->bufqueue, GstBuffer *, i); + if (is_sync_frame (mhsink, buf)) { /* found a sync frame, now extend the buffer usage to * include at least this frame. */ max_buffer_usage = MAX (max_buffer_usage, i); @@ -2062,14 +1941,14 @@ restart: /* queue exceeded max size */ queuelen--; - old = g_array_index (sink->bufqueue, GstBuffer *, i); - sink->bufqueue = g_array_remove_index (sink->bufqueue, i); + old = g_array_index (mhsink->bufqueue, GstBuffer *, i); + mhsink->bufqueue = g_array_remove_index (mhsink->bufqueue, i); /* unref tail buffer */ gst_buffer_unref (old); } /* save for stats */ - sink->buffers_queued = max_buffer_usage; + mhsink->buffers_queued = max_buffer_usage; CLIENTS_UNLOCK (sink); } @@ -2085,6 +1964,9 @@ gst_multi_socket_sink_socket_condition (GSocket * socket, GstSocketClient *client; gboolean ret = TRUE; GstMultiHandleClient *mhclient; + GstMultiHandleSink *mhsink = GST_MULTI_HANDLE_SINK (sink); + GstMultiHandleSinkClass *mhsinkclass = + GST_MULTI_HANDLE_SINK_GET_CLASS (mhsink); CLIENTS_LOCK (sink); clink = g_hash_table_lookup (sink->socket_hash, socket); @@ -2098,7 +1980,7 @@ gst_multi_socket_sink_socket_condition (GSocket * socket, if (mhclient->status != GST_CLIENT_STATUS_FLUSHING && mhclient->status != GST_CLIENT_STATUS_OK) { - gst_multi_socket_sink_remove_client_link (sink, clink); + mhsinkclass->remove_client_link (mhsink, clink); ret = FALSE; goto done; } @@ -2106,25 +1988,25 @@ gst_multi_socket_sink_socket_condition (GSocket * socket, if ((condition & G_IO_ERR)) { GST_WARNING_OBJECT (sink, "Socket %p has error", client->socket); mhclient->status = GST_CLIENT_STATUS_ERROR; - gst_multi_socket_sink_remove_client_link (sink, clink); + mhsinkclass->remove_client_link (mhsink, clink); ret = FALSE; goto done; } else if ((condition & G_IO_HUP)) { mhclient->status = GST_CLIENT_STATUS_CLOSED; - gst_multi_socket_sink_remove_client_link (sink, clink); + mhsinkclass->remove_client_link (mhsink, clink); ret = FALSE; goto done; } else if ((condition & G_IO_IN) || (condition & G_IO_PRI)) { /* handle client read */ if (!gst_multi_socket_sink_handle_client_read (sink, client)) { - gst_multi_socket_sink_remove_client_link (sink, clink); + mhsinkclass->remove_client_link (mhsink, clink); ret = FALSE; goto done; } } else if ((condition & G_IO_OUT)) { /* handle client write */ if (!gst_multi_socket_sink_handle_client_write (sink, client)) { - gst_multi_socket_sink_remove_client_link (sink, clink); + mhsinkclass->remove_client_link (mhsink, clink); ret = FALSE; goto done; } @@ -2142,15 +2024,15 @@ gst_multi_socket_sink_timeout (GstMultiSocketSink * sink) GstClockTime now; GTimeVal nowtv; GList *clients; - GstMultiHandleSink *mhsink; - - mhsink = GST_MULTI_HANDLE_SINK (sink); + GstMultiHandleSink *mhsink = GST_MULTI_HANDLE_SINK (sink); + GstMultiHandleSinkClass *mhsinkclass = + GST_MULTI_HANDLE_SINK_GET_CLASS (mhsink); g_get_current_time (&nowtv); now = GST_TIMEVAL_TO_TIME (nowtv); CLIENTS_LOCK (sink); - for (clients = sink->clients; clients; clients = clients->next) { + for (clients = mhsink->clients; clients; clients = clients->next) { GstSocketClient *client; GstMultiHandleClient *mhclient; @@ -2159,7 +2041,7 @@ gst_multi_socket_sink_timeout (GstMultiSocketSink * sink) if (mhsink->timeout > 0 && now - mhclient->last_activity_time > mhsink->timeout) { mhclient->status = GST_CLIENT_STATUS_SLOW; - gst_multi_socket_sink_remove_client_link (sink, clients); + mhsinkclass->remove_client_link (mhsink, clients); } } CLIENTS_UNLOCK (sink); @@ -2170,14 +2052,12 @@ gst_multi_socket_sink_timeout (GstMultiSocketSink * sink) /* we handle the client communication in another thread so that we do not block * the gstreamer thread while we select() on the client fds */ static gpointer -gst_multi_socket_sink_thread (GstMultiSocketSink * sink) +gst_multi_socket_sink_thread (GstMultiHandleSink * mhsink) { + GstMultiSocketSink *sink = GST_MULTI_SOCKET_SINK (mhsink); GSource *timeout = NULL; - GstMultiHandleSink *mhsink; - mhsink = GST_MULTI_HANDLE_SINK (sink); - - while (sink->running) { + while (mhsink->running) { if (mhsink->timeout > 0) { timeout = g_timeout_source_new (mhsink->timeout / GST_MSECOND); @@ -2216,7 +2096,7 @@ gst_multi_socket_sink_render (GstBaseSink * bsink, GstBuffer * buf) mhsink = GST_MULTI_HANDLE_SINK (sink); g_return_val_if_fail (GST_OBJECT_FLAG_IS_SET (sink, - GST_MULTI_SOCKET_SINK_OPEN), GST_FLOW_FLUSHING); + GST_MULTI_HANDLE_SINK_OPEN), GST_FLOW_FLUSHING); #if 0 /* since we check every buffer for streamheader caps, we need to make @@ -2272,9 +2152,9 @@ gst_multi_socket_sink_render (GstBaseSink * bsink, GstBuffer * buf) if (in_caps && sink->previous_buffer_in_caps == FALSE) { GST_DEBUG_OBJECT (sink, "receiving new HEADER buffers, clearing old streamheader"); - g_slist_foreach (sink->streamheader, (GFunc) gst_mini_object_unref, NULL); - g_slist_free (sink->streamheader); - sink->streamheader = NULL; + g_slist_foreach (mhsink->streamheader, (GFunc) gst_mini_object_unref, NULL); + g_slist_free (mhsink->streamheader); + mhsink->streamheader = NULL; } /* save the current in_caps */ @@ -2291,7 +2171,7 @@ gst_multi_socket_sink_render (GstBaseSink * bsink, GstBuffer * buf) if (in_caps) { GST_DEBUG_OBJECT (sink, "appending HEADER buffer with length %" G_GSIZE_FORMAT " to streamheader", gst_buffer_get_size (buf)); - sink->streamheader = g_slist_append (sink->streamheader, buf); + mhsink->streamheader = g_slist_append (mhsink->streamheader, buf); } else { /* queue the buffer, this is a regular data buffer. */ gst_multi_socket_sink_queue_buffer (sink, buf); @@ -2345,12 +2225,6 @@ gst_multi_socket_sink_set_property (GObject * object, guint prop_id, multisocketsink->qos_dscp = g_value_get_int (value); setup_dscp (multisocketsink); break; - case PROP_HANDLE_READ: - multisocketsink->handle_read = g_value_get_boolean (value); - break; - case PROP_RESEND_STREAMHEADER: - multisocketsink->resend_streamheader = g_value_get_boolean (value); - break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -2373,15 +2247,6 @@ gst_multi_socket_sink_get_property (GObject * object, guint prop_id, case PROP_BUFFERS_SOFT_MAX: g_value_set_int (value, multisocketsink->units_soft_max); break; - case PROP_BUFFERS_QUEUED: - g_value_set_uint (value, multisocketsink->buffers_queued); - break; - case PROP_BYTES_QUEUED: - g_value_set_uint (value, multisocketsink->bytes_queued); - break; - case PROP_TIME_QUEUED: - g_value_set_uint64 (value, multisocketsink->time_queued); - break; case PROP_UNIT_TYPE: g_value_set_enum (value, multisocketsink->unit_type); break; @@ -2400,12 +2265,6 @@ gst_multi_socket_sink_get_property (GObject * object, guint prop_id, case PROP_QOS_DSCP: g_value_set_int (value, multisocketsink->qos_dscp); break; - case PROP_HANDLE_READ: - g_value_set_boolean (value, multisocketsink->handle_read); - break; - case PROP_RESEND_STREAMHEADER: - g_value_set_boolean (value, multisocketsink->resend_streamheader); - break; case PROP_NUM_SOCKETS: g_value_set_uint (value, g_hash_table_size (multisocketsink->socket_hash)); @@ -2417,30 +2276,18 @@ gst_multi_socket_sink_get_property (GObject * object, guint prop_id, } } - -/* create a socket for sending to remote machine */ static gboolean -gst_multi_socket_sink_start (GstBaseSink * bsink) +gst_multi_socket_sink_start_pre (GstMultiHandleSink * mhsink) { - GstMultiSocketSinkClass *fclass; - GstMultiSocketSink *this; - GstMultiHandleSink *mhsink; - + GstMultiSocketSink *mssink = GST_MULTI_SOCKET_SINK (mhsink); GList *clients; - if (GST_OBJECT_FLAG_IS_SET (bsink, GST_MULTI_HANDLE_SINK_OPEN)) - return TRUE; - - this = GST_MULTI_SOCKET_SINK (bsink); - mhsink = GST_MULTI_HANDLE_SINK (bsink); - fclass = GST_MULTI_SOCKET_SINK_GET_CLASS (this); - - GST_INFO_OBJECT (this, "starting"); + GST_INFO_OBJECT (mssink, "starting"); - this->main_context = g_main_context_new (); + mssink->main_context = g_main_context_new (); - CLIENTS_LOCK (this); - for (clients = this->clients; clients; clients = clients->next) { + CLIENTS_LOCK (mssink); + for (clients = mhsink->clients; clients; clients = clients->next) { GstSocketClient *client; client = clients->data; @@ -2448,152 +2295,46 @@ gst_multi_socket_sink_start (GstBaseSink * bsink) continue; client->source = g_socket_create_source (client->socket, - G_IO_IN | G_IO_OUT | G_IO_PRI | G_IO_ERR | G_IO_HUP, this->cancellable); + G_IO_IN | G_IO_OUT | G_IO_PRI | G_IO_ERR | G_IO_HUP, + mssink->cancellable); g_source_set_callback (client->source, (GSourceFunc) gst_multi_socket_sink_socket_condition, - gst_object_ref (this), (GDestroyNotify) gst_object_unref); - g_source_attach (client->source, this->main_context); + gst_object_ref (mssink), (GDestroyNotify) gst_object_unref); + g_source_attach (client->source, mssink->main_context); } - CLIENTS_UNLOCK (this); - - this->streamheader = NULL; - mhsink->bytes_to_serve = 0; - mhsink->bytes_served = 0; - - if (fclass->init) { - fclass->init (this); - } - - this->running = TRUE; - - this->thread = g_thread_new ("multisocketsink", - (GThreadFunc) gst_multi_socket_sink_thread, this); - - GST_OBJECT_FLAG_SET (this, GST_MULTI_HANDLE_SINK_OPEN); + CLIENTS_UNLOCK (mssink); return TRUE; } + static gboolean multisocketsink_hash_remove (gpointer key, gpointer value, gpointer data) { return TRUE; } -static gboolean -gst_multi_socket_sink_stop (GstBaseSink * bsink) +static void +gst_multi_socket_sink_stop_pre (GstMultiHandleSink * mhsink) { - GstMultiSocketSinkClass *fclass; - GstMultiHandleSinkClass *mhclass; - GstMultiSocketSink *this; - GstBuffer *buf; - gint i; + GstMultiSocketSink *mssink = GST_MULTI_SOCKET_SINK (mhsink); - this = GST_MULTI_SOCKET_SINK (bsink); - fclass = GST_MULTI_SOCKET_SINK_GET_CLASS (this); - mhclass = GST_MULTI_HANDLE_SINK_GET_CLASS (this); - - if (!GST_OBJECT_FLAG_IS_SET (bsink, GST_MULTI_HANDLE_SINK_OPEN)) - return TRUE; - - this->running = FALSE; - - if (this->main_context) - g_main_context_wakeup (this->main_context); - - if (this->thread) { - GST_DEBUG_OBJECT (this, "joining thread"); - g_thread_join (this->thread); - GST_DEBUG_OBJECT (this, "joined thread"); - this->thread = NULL; - } - - /* free the clients */ - mhclass->clear (GST_MULTI_HANDLE_SINK (this)); - - if (this->streamheader) { - g_slist_foreach (this->streamheader, (GFunc) gst_mini_object_unref, NULL); - g_slist_free (this->streamheader); - this->streamheader = NULL; - } - - if (fclass->close) - fclass->close (this); - - if (this->main_context) { - g_main_context_unref (this->main_context); - this->main_context = NULL; - } - - g_hash_table_foreach_remove (this->socket_hash, multisocketsink_hash_remove, - this); - - /* remove all queued buffers */ - if (this->bufqueue) { - GST_DEBUG_OBJECT (this, "Emptying bufqueue with %d buffers", - this->bufqueue->len); - for (i = this->bufqueue->len - 1; i >= 0; --i) { - buf = g_array_index (this->bufqueue, GstBuffer *, i); - GST_LOG_OBJECT (this, "Removing buffer %p (%d) with refcount %d", buf, i, - GST_MINI_OBJECT_REFCOUNT (buf)); - gst_buffer_unref (buf); - this->bufqueue = g_array_remove_index (this->bufqueue, i); - } - /* freeing the array is done in _finalize */ - } - GST_OBJECT_FLAG_UNSET (this, GST_MULTI_HANDLE_SINK_OPEN); - - return TRUE; + if (mssink->main_context) + g_main_context_wakeup (mssink->main_context); } -static GstStateChangeReturn -gst_multi_socket_sink_change_state (GstElement * element, - GstStateChange transition) +static void +gst_multi_socket_sink_stop_post (GstMultiHandleSink * mhsink) { - GstMultiSocketSink *sink; - GstStateChangeReturn ret; - - sink = GST_MULTI_SOCKET_SINK (element); + GstMultiSocketSink *mssink = GST_MULTI_SOCKET_SINK (mhsink); - /* we disallow changing the state from the streaming thread */ - if (g_thread_self () == sink->thread) - return GST_STATE_CHANGE_FAILURE; - - - switch (transition) { - case GST_STATE_CHANGE_NULL_TO_READY: - if (!gst_multi_socket_sink_start (GST_BASE_SINK (sink))) - goto start_failed; - break; - case GST_STATE_CHANGE_READY_TO_PAUSED: - break; - case GST_STATE_CHANGE_PAUSED_TO_PLAYING: - break; - default: - break; + if (mssink->main_context) { + g_main_context_unref (mssink->main_context); + mssink->main_context = NULL; } - ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); - - switch (transition) { - case GST_STATE_CHANGE_PLAYING_TO_PAUSED: - break; - case GST_STATE_CHANGE_PAUSED_TO_READY: - break; - case GST_STATE_CHANGE_READY_TO_NULL: - gst_multi_socket_sink_stop (GST_BASE_SINK (sink)); - break; - default: - break; - } - return ret; - - /* ERRORS */ -start_failed: - { - /* error message was posted */ - return GST_STATE_CHANGE_FAILURE; - } + g_hash_table_foreach_remove (mssink->socket_hash, multisocketsink_hash_remove, + mssink); } static gboolean diff --git a/gst/tcp/gstmultisocketsink.h b/gst/tcp/gstmultisocketsink.h index 286605b..fa8030a 100644 --- a/gst/tcp/gstmultisocketsink.h +++ b/gst/tcp/gstmultisocketsink.h @@ -75,25 +75,15 @@ struct _GstMultiSocketSink { GstMultiHandleSink element; /*< private >*/ - GRecMutex clientslock; /* lock to protect the clients list */ - GList *clients; /* list of clients we are serving */ GHashTable *socket_hash; /* index on socket to client */ - guint clients_cookie; /* Cookie to detect changes to the clients list */ GMainContext *main_context; GCancellable *cancellable; - GSList *streamheader; /* GSList of GstBuffers to use as streamheader */ gboolean previous_buffer_in_caps; guint mtu; gint qos_dscp; - gboolean handle_read; - - GArray *bufqueue; /* global queue of buffers */ - - gboolean running; /* the thread state */ - GThread *thread; /* the sender thread */ /* these values are used to check if a client is reading fast * enough and to control receovery */ @@ -104,13 +94,6 @@ struct _GstMultiSocketSink { GstFormat def_burst_format; guint64 def_burst_value; - gboolean resend_streamheader; /* resend streamheader if it changes */ - - /* stats */ - gint buffers_queued; /* number of queued buffers */ - gint bytes_queued; /* number of queued bytes */ - gint time_queued; /* number of queued time */ - guint8 header_flags; }; @@ -128,8 +111,6 @@ struct _GstMultiSocketSinkClass { GstStructure* (*get_stats) (GstMultiSocketSink *sink, GSocket *socket); /* vtable */ - gboolean (*init) (GstMultiSocketSink *sink); - gboolean (*close) (GstMultiSocketSink *sink); void (*removed) (GstMultiSocketSink *sink, GSocket *socket); /* signals */ @@ -141,7 +122,7 @@ struct _GstMultiSocketSinkClass { GType gst_multi_socket_sink_get_type (void); void gst_multi_socket_sink_add (GstMultiSocketSink *sink, GSocket *socket); -void gst_multi_socket_sink_add_full (GstMultiSocketSink *sink, GSocket *socket, GstSyncMethod sync, +void gst_multi_socket_sink_add_full (GstMultiSocketSink *sink, GSocket *socket, GstSyncMethod sync, GstFormat min_format, guint64 min_value, GstFormat max_format, guint64 max_value); void gst_multi_socket_sink_remove (GstMultiSocketSink *sink, GSocket *socket); diff --git a/gst/tcp/gsttcpserversink.c b/gst/tcp/gsttcpserversink.c index e1f4e68..62acaaf 100644 --- a/gst/tcp/gsttcpserversink.c +++ b/gst/tcp/gsttcpserversink.c @@ -57,8 +57,8 @@ enum static void gst_tcp_server_sink_finalize (GObject * gobject); -static gboolean gst_tcp_server_sink_init_send (GstMultiSocketSink * this); -static gboolean gst_tcp_server_sink_close (GstMultiSocketSink * this); +static gboolean gst_tcp_server_sink_init_send (GstMultiHandleSink * this); +static gboolean gst_tcp_server_sink_close (GstMultiHandleSink * this); static void gst_tcp_server_sink_removed (GstMultiSocketSink * sink, GSocket * socket); @@ -76,10 +76,12 @@ gst_tcp_server_sink_class_init (GstTCPServerSinkClass * klass) { GObjectClass *gobject_class; GstElementClass *gstelement_class; + GstMultiHandleSinkClass *gstmultihandlesink_class; GstMultiSocketSinkClass *gstmultifdsink_class; gobject_class = (GObjectClass *) klass; gstelement_class = (GstElementClass *) klass; + gstmultihandlesink_class = (GstMultiHandleSinkClass *) klass; gstmultifdsink_class = (GstMultiSocketSinkClass *) klass; gobject_class->set_property = gst_tcp_server_sink_set_property; @@ -99,8 +101,8 @@ gst_tcp_server_sink_class_init (GstTCPServerSinkClass * klass) "Send data as a server over the network via TCP", "Thomas Vander Stichele "); - gstmultifdsink_class->init = gst_tcp_server_sink_init_send; - gstmultifdsink_class->close = gst_tcp_server_sink_close; + gstmultihandlesink_class->init = gst_tcp_server_sink_init_send; + gstmultihandlesink_class->close = gst_tcp_server_sink_close; gstmultifdsink_class->removed = gst_tcp_server_sink_removed; GST_DEBUG_CATEGORY_INIT (tcpserversink_debug, "tcpserversink", 0, "TCP sink"); @@ -263,7 +265,7 @@ gst_tcp_server_sink_get_property (GObject * object, guint prop_id, /* create a socket for sending to remote machine */ static gboolean -gst_tcp_server_sink_init_send (GstMultiSocketSink * parent) +gst_tcp_server_sink_init_send (GstMultiHandleSink * parent) { GstTCPServerSink *this = GST_TCP_SERVER_SINK (parent); GError *err = NULL; @@ -371,7 +373,7 @@ bind_failed: } g_clear_error (&err); g_object_unref (saddr); - gst_tcp_server_sink_close (&this->element); + gst_tcp_server_sink_close (GST_MULTI_HANDLE_SINK (&this->element)); return FALSE; } listen_failed: @@ -384,13 +386,13 @@ listen_failed: this->server_port, err->message)); } g_clear_error (&err); - gst_tcp_server_sink_close (&this->element); + gst_tcp_server_sink_close (GST_MULTI_HANDLE_SINK (&this->element)); return FALSE; } } static gboolean -gst_tcp_server_sink_close (GstMultiSocketSink * parent) +gst_tcp_server_sink_close (GstMultiHandleSink * parent) { GstTCPServerSink *this = GST_TCP_SERVER_SINK (parent);