| /* GStreamer |
| * Copyright (C) <2007> Wim Taymans <wim.taymans@gmail.com> |
| * Copyright (C) <2009> Jarkko Palviainen <jarkko.palviainen@sesca.com> |
| * Copyright (C) <2012> Collabora Ltd. |
| * Author: Sebastian Dröge <sebastian.droege@collabora.co.uk> |
| * |
| * 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., 51 Franklin St, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| /** |
| * SECTION:element-multiudpsink |
| * @see_also: udpsink, multifdsink |
| * |
| * multiudpsink is a network sink that sends UDP packets to multiple |
| * clients. |
| * It can be combined with rtp payload encoders to implement RTP streaming. |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| #include "gstmultiudpsink.h" |
| |
| #include <string.h> |
| #ifdef HAVE_SYS_SOCKET_H |
| #include <sys/socket.h> |
| #endif |
| |
| #ifndef G_OS_WIN32 |
| #include <netinet/in.h> |
| #endif |
| |
| #include "gst/glib-compat-private.h" |
| |
| GST_DEBUG_CATEGORY_STATIC (multiudpsink_debug); |
| #define GST_CAT_DEFAULT (multiudpsink_debug) |
| |
| #define UDP_MAX_SIZE 65507 |
| |
| static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", |
| GST_PAD_SINK, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS_ANY); |
| |
| /* MultiUDPSink signals and args */ |
| enum |
| { |
| /* methods */ |
| SIGNAL_ADD, |
| SIGNAL_REMOVE, |
| SIGNAL_CLEAR, |
| SIGNAL_GET_STATS, |
| |
| /* signals */ |
| SIGNAL_CLIENT_ADDED, |
| SIGNAL_CLIENT_REMOVED, |
| |
| /* FILL ME */ |
| LAST_SIGNAL |
| }; |
| |
| #define DEFAULT_SOCKET NULL |
| #define DEFAULT_CLOSE_SOCKET TRUE |
| #define DEFAULT_USED_SOCKET NULL |
| #define DEFAULT_CLIENTS NULL |
| /* FIXME, this should be disabled by default, we don't need to join a multicast |
| * group for sending, if this socket is also used for receiving, it should |
| * be configured in the element that does the receive. */ |
| #define DEFAULT_AUTO_MULTICAST TRUE |
| #define DEFAULT_MULTICAST_IFACE NULL |
| #define DEFAULT_TTL 64 |
| #define DEFAULT_TTL_MC 1 |
| #define DEFAULT_LOOP TRUE |
| #define DEFAULT_FORCE_IPV4 FALSE |
| #define DEFAULT_QOS_DSCP -1 |
| #define DEFAULT_SEND_DUPLICATES TRUE |
| #define DEFAULT_BUFFER_SIZE 0 |
| #define DEFAULT_BIND_ADDRESS NULL |
| #define DEFAULT_BIND_PORT 0 |
| |
| enum |
| { |
| PROP_0, |
| PROP_BYTES_TO_SERVE, |
| PROP_BYTES_SERVED, |
| PROP_SOCKET, |
| PROP_SOCKET_V6, |
| PROP_CLOSE_SOCKET, |
| PROP_USED_SOCKET, |
| PROP_USED_SOCKET_V6, |
| PROP_CLIENTS, |
| PROP_AUTO_MULTICAST, |
| PROP_MULTICAST_IFACE, |
| PROP_TTL, |
| PROP_TTL_MC, |
| PROP_LOOP, |
| PROP_FORCE_IPV4, |
| PROP_QOS_DSCP, |
| PROP_SEND_DUPLICATES, |
| PROP_BUFFER_SIZE, |
| PROP_BIND_ADDRESS, |
| PROP_BIND_PORT |
| }; |
| |
| static void gst_multiudpsink_finalize (GObject * object); |
| |
| static GstFlowReturn gst_multiudpsink_render (GstBaseSink * sink, |
| GstBuffer * buffer); |
| static GstFlowReturn gst_multiudpsink_render_list (GstBaseSink * bsink, |
| GstBufferList * buffer_list); |
| |
| static gboolean gst_multiudpsink_start (GstBaseSink * bsink); |
| static gboolean gst_multiudpsink_stop (GstBaseSink * bsink); |
| static gboolean gst_multiudpsink_unlock (GstBaseSink * bsink); |
| static gboolean gst_multiudpsink_unlock_stop (GstBaseSink * bsink); |
| |
| static void gst_multiudpsink_set_property (GObject * object, guint prop_id, |
| const GValue * value, GParamSpec * pspec); |
| static void gst_multiudpsink_get_property (GObject * object, guint prop_id, |
| GValue * value, GParamSpec * pspec); |
| |
| static void gst_multiudpsink_add_internal (GstMultiUDPSink * sink, |
| const gchar * host, gint port, gboolean lock); |
| static void gst_multiudpsink_clear_internal (GstMultiUDPSink * sink, |
| gboolean lock); |
| |
| static guint gst_multiudpsink_signals[LAST_SIGNAL] = { 0 }; |
| |
| #define gst_multiudpsink_parent_class parent_class |
| G_DEFINE_TYPE (GstMultiUDPSink, gst_multiudpsink, GST_TYPE_BASE_SINK); |
| |
| static void |
| gst_multiudpsink_class_init (GstMultiUDPSinkClass * 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_multiudpsink_set_property; |
| gobject_class->get_property = gst_multiudpsink_get_property; |
| gobject_class->finalize = gst_multiudpsink_finalize; |
| |
| /** |
| * GstMultiUDPSink::add: |
| * @gstmultiudpsink: the sink on which the signal is emitted |
| * @host: the hostname/IP address of the client to add |
| * @port: the port of the client to add |
| * |
| * Add a client with destination @host and @port to the list of |
| * clients. When the same host/port pair is added multiple times, the |
| * send-duplicates property defines if the packets are sent multiple times to |
| * the same host/port pair or not. |
| * |
| * When a host/port pair is added multiple times, an equal amount of remove |
| * calls must be performed to actually remove the host/port pair from the list |
| * of destinations. |
| */ |
| gst_multiudpsink_signals[SIGNAL_ADD] = |
| g_signal_new ("add", G_TYPE_FROM_CLASS (klass), |
| G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, |
| G_STRUCT_OFFSET (GstMultiUDPSinkClass, add), |
| NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 2, |
| G_TYPE_STRING, G_TYPE_INT); |
| /** |
| * GstMultiUDPSink::remove: |
| * @gstmultiudpsink: the sink on which the signal is emitted |
| * @host: the hostname/IP address of the client to remove |
| * @port: the port of the client to remove |
| * |
| * Remove the client with destination @host and @port from the list of |
| * clients. |
| */ |
| gst_multiudpsink_signals[SIGNAL_REMOVE] = |
| g_signal_new ("remove", G_TYPE_FROM_CLASS (klass), |
| G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, |
| G_STRUCT_OFFSET (GstMultiUDPSinkClass, remove), |
| NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 2, |
| G_TYPE_STRING, G_TYPE_INT); |
| /** |
| * GstMultiUDPSink::clear: |
| * @gstmultiudpsink: the sink on which the signal is emitted |
| * |
| * Clear the list of clients. |
| */ |
| gst_multiudpsink_signals[SIGNAL_CLEAR] = |
| g_signal_new ("clear", G_TYPE_FROM_CLASS (klass), |
| G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, |
| G_STRUCT_OFFSET (GstMultiUDPSinkClass, clear), |
| NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 0); |
| /** |
| * GstMultiUDPSink::get-stats: |
| * @gstmultiudpsink: the sink on which the signal is emitted |
| * @host: the hostname/IP address of the client to get stats on |
| * @port: the port of the client to get stats on |
| * |
| * Get the statistics of the client with destination @host and @port. |
| * |
| * Returns: a GstStructure: bytes_sent, packets_sent, |
| * connect_time (in epoch seconds), disconnect_time (in epoch seconds) |
| */ |
| gst_multiudpsink_signals[SIGNAL_GET_STATS] = |
| g_signal_new ("get-stats", G_TYPE_FROM_CLASS (klass), |
| G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, |
| G_STRUCT_OFFSET (GstMultiUDPSinkClass, get_stats), |
| NULL, NULL, g_cclosure_marshal_generic, GST_TYPE_STRUCTURE, 2, |
| G_TYPE_STRING, G_TYPE_INT); |
| /** |
| * GstMultiUDPSink::client-added: |
| * @gstmultiudpsink: the sink emitting the signal |
| * @host: the hostname/IP address of the added client |
| * @port: the port of the added client |
| * |
| * Signal emited when a new client is added to the list of |
| * clients. |
| */ |
| gst_multiudpsink_signals[SIGNAL_CLIENT_ADDED] = |
| g_signal_new ("client-added", G_TYPE_FROM_CLASS (klass), |
| G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstMultiUDPSinkClass, client_added), |
| NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 2, |
| G_TYPE_STRING, G_TYPE_INT); |
| /** |
| * GstMultiUDPSink::client-removed: |
| * @gstmultiudpsink: the sink emitting the signal |
| * @host: the hostname/IP address of the removed client |
| * @port: the port of the removed client |
| * |
| * Signal emited when a client is removed from the list of |
| * clients. |
| */ |
| gst_multiudpsink_signals[SIGNAL_CLIENT_REMOVED] = |
| g_signal_new ("client-removed", G_TYPE_FROM_CLASS (klass), |
| G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstMultiUDPSinkClass, |
| client_removed), NULL, NULL, g_cclosure_marshal_generic, |
| G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_INT); |
| |
| g_object_class_install_property (G_OBJECT_CLASS (klass), 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 (G_OBJECT_CLASS (klass), PROP_BYTES_SERVED, |
| g_param_spec_uint64 ("bytes-served", "Bytes served", |
| "Total number of bytes sent to all clients", 0, G_MAXUINT64, 0, |
| G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (gobject_class, PROP_SOCKET, |
| g_param_spec_object ("socket", "Socket Handle", |
| "Socket to use for UDP sending. (NULL == allocate)", |
| G_TYPE_SOCKET, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (gobject_class, PROP_SOCKET_V6, |
| g_param_spec_object ("socket-v6", "Socket Handle IPv6", |
| "Socket to use for UDPv6 sending. (NULL == allocate)", |
| G_TYPE_SOCKET, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (gobject_class, PROP_CLOSE_SOCKET, |
| g_param_spec_boolean ("close-socket", "Close socket", |
| "Close socket if passed as property on state change", |
| DEFAULT_CLOSE_SOCKET, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (gobject_class, PROP_USED_SOCKET, |
| g_param_spec_object ("used-socket", "Used Socket Handle", |
| "Socket currently in use for UDP sending. (NULL == no socket)", |
| G_TYPE_SOCKET, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (gobject_class, PROP_USED_SOCKET_V6, |
| g_param_spec_object ("used-socket-v6", "Used Socket Handle IPv6", |
| "Socket currently in use for UDPv6 sending. (NULL == no socket)", |
| G_TYPE_SOCKET, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (gobject_class, PROP_CLIENTS, |
| g_param_spec_string ("clients", "Clients", |
| "A comma separated list of host:port pairs with destinations", |
| DEFAULT_CLIENTS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (gobject_class, PROP_AUTO_MULTICAST, |
| g_param_spec_boolean ("auto-multicast", |
| "Automatically join/leave multicast groups", |
| "Automatically join/leave the multicast groups, FALSE means user" |
| " has to do it himself", DEFAULT_AUTO_MULTICAST, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (gobject_class, PROP_MULTICAST_IFACE, |
| g_param_spec_string ("multicast-iface", "Multicast Interface", |
| "The network interface on which to join the multicast group", |
| DEFAULT_MULTICAST_IFACE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (gobject_class, PROP_TTL, |
| g_param_spec_int ("ttl", "Unicast TTL", |
| "Used for setting the unicast TTL parameter", |
| 0, 255, DEFAULT_TTL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (gobject_class, PROP_TTL_MC, |
| g_param_spec_int ("ttl-mc", "Multicast TTL", |
| "Used for setting the multicast TTL parameter", |
| 0, 255, DEFAULT_TTL_MC, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (gobject_class, PROP_LOOP, |
| g_param_spec_boolean ("loop", "Multicast Loopback", |
| "Used for setting the multicast loop parameter. TRUE = enable," |
| " FALSE = disable", DEFAULT_LOOP, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| /** |
| * GstMultiUDPSink::force-ipv4: |
| * |
| * Force the use of an IPv4 socket. |
| * |
| * Since: 1.0.2 |
| */ |
| #ifndef GST_REMOVE_DEPRECATED |
| g_object_class_install_property (gobject_class, PROP_FORCE_IPV4, |
| g_param_spec_boolean ("force-ipv4", "Force IPv4", |
| "Forcing the use of an IPv4 socket (DEPRECATED, has no effect anymore)", |
| DEFAULT_FORCE_IPV4, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_DEPRECATED)); |
| #endif |
| g_object_class_install_property (G_OBJECT_CLASS (klass), 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)); |
| /** |
| * GstMultiUDPSink::send-duplicates: |
| * |
| * When a host/port pair is added mutliple times, send the packet to the host |
| * multiple times as well. |
| */ |
| g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SEND_DUPLICATES, |
| g_param_spec_boolean ("send-duplicates", "Send Duplicates", |
| "When a distination/port pair is added multiple times, send packets " |
| "multiple times as well", DEFAULT_SEND_DUPLICATES, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_BUFFER_SIZE, |
| g_param_spec_int ("buffer-size", "Buffer Size", |
| "Size of the kernel send buffer in bytes, 0=default", 0, G_MAXINT, |
| DEFAULT_BUFFER_SIZE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| g_object_class_install_property (gobject_class, PROP_BIND_ADDRESS, |
| g_param_spec_string ("bind-address", "Bind Address", |
| "Address to bind the socket to", DEFAULT_BIND_ADDRESS, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (gobject_class, PROP_BIND_PORT, |
| g_param_spec_int ("bind-port", "Bind Port", |
| "Port to bind the socket to", 0, G_MAXUINT16, |
| DEFAULT_BIND_PORT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| gst_element_class_add_static_pad_template (gstelement_class, &sink_template); |
| |
| gst_element_class_set_static_metadata (gstelement_class, "UDP packet sender", |
| "Sink/Network", |
| "Send data over the network via UDP to one or multiple recipients " |
| "which can be added or removed at runtime using action signals", |
| "Wim Taymans <wim.taymans@gmail.com>"); |
| |
| gstbasesink_class->render = gst_multiudpsink_render; |
| gstbasesink_class->render_list = gst_multiudpsink_render_list; |
| gstbasesink_class->start = gst_multiudpsink_start; |
| gstbasesink_class->stop = gst_multiudpsink_stop; |
| gstbasesink_class->unlock = gst_multiudpsink_unlock; |
| gstbasesink_class->unlock_stop = gst_multiudpsink_unlock_stop; |
| klass->add = gst_multiudpsink_add; |
| klass->remove = gst_multiudpsink_remove; |
| klass->clear = gst_multiudpsink_clear; |
| klass->get_stats = gst_multiudpsink_get_stats; |
| |
| GST_DEBUG_CATEGORY_INIT (multiudpsink_debug, "multiudpsink", 0, "UDP sink"); |
| } |
| |
| static void |
| gst_multiudpsink_create_cancellable (GstMultiUDPSink * sink) |
| { |
| GPollFD pollfd; |
| |
| sink->cancellable = g_cancellable_new (); |
| sink->made_cancel_fd = g_cancellable_make_pollfd (sink->cancellable, &pollfd); |
| } |
| |
| static void |
| gst_multiudpsink_free_cancellable (GstMultiUDPSink * sink) |
| { |
| if (sink->made_cancel_fd) { |
| g_cancellable_release_fd (sink->cancellable); |
| sink->made_cancel_fd = FALSE; |
| } |
| g_object_unref (sink->cancellable); |
| sink->cancellable = NULL; |
| } |
| |
| static void |
| gst_multiudpsink_init (GstMultiUDPSink * sink) |
| { |
| guint max_mem; |
| |
| g_mutex_init (&sink->client_lock); |
| sink->clients = NULL; |
| sink->num_v4_unique = 0; |
| sink->num_v4_all = 0; |
| sink->num_v6_unique = 0; |
| sink->num_v6_all = 0; |
| |
| sink->socket = DEFAULT_SOCKET; |
| sink->socket_v6 = DEFAULT_SOCKET; |
| sink->used_socket = DEFAULT_USED_SOCKET; |
| sink->used_socket_v6 = DEFAULT_USED_SOCKET; |
| sink->close_socket = DEFAULT_CLOSE_SOCKET; |
| sink->external_socket = (sink->socket != NULL); |
| sink->auto_multicast = DEFAULT_AUTO_MULTICAST; |
| sink->ttl = DEFAULT_TTL; |
| sink->ttl_mc = DEFAULT_TTL_MC; |
| sink->loop = DEFAULT_LOOP; |
| sink->force_ipv4 = DEFAULT_FORCE_IPV4; |
| sink->qos_dscp = DEFAULT_QOS_DSCP; |
| sink->send_duplicates = DEFAULT_SEND_DUPLICATES; |
| sink->multi_iface = g_strdup (DEFAULT_MULTICAST_IFACE); |
| |
| gst_multiudpsink_create_cancellable (sink); |
| |
| /* pre-allocate OutputVector, MapInfo and OutputMessage arrays |
| * for use in the render and render_list functions */ |
| max_mem = gst_buffer_get_max_memory (); |
| |
| sink->n_vecs = max_mem; |
| sink->vecs = g_new (GOutputVector, sink->n_vecs); |
| |
| sink->n_maps = max_mem; |
| sink->maps = g_new (GstMapInfo, sink->n_maps); |
| |
| sink->n_messages = 1; |
| sink->messages = g_new (GstOutputMessage, sink->n_messages); |
| |
| /* we assume that the number of memories per buffer can fit into a guint8 */ |
| g_warn_if_fail (max_mem <= G_MAXUINT8); |
| } |
| |
| static GstUDPClient * |
| gst_udp_client_new (GstMultiUDPSink * sink, const gchar * host, gint port) |
| { |
| GstUDPClient *client; |
| GInetAddress *addr; |
| GResolver *resolver; |
| GError *err = NULL; |
| |
| addr = g_inet_address_new_from_string (host); |
| if (!addr) { |
| GList *results; |
| |
| resolver = g_resolver_get_default (); |
| results = |
| g_resolver_lookup_by_name (resolver, host, sink->cancellable, &err); |
| if (!results) |
| goto name_resolve; |
| addr = G_INET_ADDRESS (g_object_ref (results->data)); |
| |
| g_resolver_free_addresses (results); |
| g_object_unref (resolver); |
| } |
| #ifndef GST_DISABLE_GST_DEBUG |
| { |
| gchar *ip = g_inet_address_to_string (addr); |
| |
| GST_DEBUG_OBJECT (sink, "IP address for host %s is %s", host, ip); |
| g_free (ip); |
| } |
| #endif |
| |
| client = g_slice_new0 (GstUDPClient); |
| client->ref_count = 1; |
| client->add_count = 0; |
| client->host = g_strdup (host); |
| client->port = port; |
| client->addr = g_inet_socket_address_new (addr, port); |
| g_object_unref (addr); |
| |
| return client; |
| |
| name_resolve: |
| { |
| g_clear_error (&err); |
| g_object_unref (resolver); |
| |
| return NULL; |
| } |
| } |
| |
| /* call with client lock held */ |
| static void |
| gst_udp_client_unref (GstUDPClient * client) |
| { |
| if (--client->ref_count == 0) { |
| g_object_unref (client->addr); |
| g_free (client->host); |
| g_slice_free (GstUDPClient, client); |
| } |
| } |
| |
| /* call with client lock held */ |
| static inline GstUDPClient * |
| gst_udp_client_ref (GstUDPClient * client) |
| { |
| ++client->ref_count; |
| return client; |
| } |
| |
| static gint |
| client_compare (GstUDPClient * a, GstUDPClient * b) |
| { |
| if ((a->port == b->port) && (strcmp (a->host, b->host) == 0)) |
| return 0; |
| |
| return 1; |
| } |
| |
| static void |
| gst_multiudpsink_finalize (GObject * object) |
| { |
| GstMultiUDPSink *sink; |
| |
| sink = GST_MULTIUDPSINK (object); |
| |
| g_list_foreach (sink->clients, (GFunc) gst_udp_client_unref, NULL); |
| g_list_free (sink->clients); |
| |
| if (sink->socket) |
| g_object_unref (sink->socket); |
| sink->socket = NULL; |
| |
| if (sink->socket_v6) |
| g_object_unref (sink->socket_v6); |
| sink->socket_v6 = NULL; |
| |
| if (sink->used_socket) |
| g_object_unref (sink->used_socket); |
| sink->used_socket = NULL; |
| |
| if (sink->used_socket_v6) |
| g_object_unref (sink->used_socket_v6); |
| sink->used_socket_v6 = NULL; |
| |
| gst_multiudpsink_free_cancellable (sink); |
| |
| g_free (sink->multi_iface); |
| sink->multi_iface = NULL; |
| |
| g_free (sink->vecs); |
| sink->vecs = NULL; |
| g_free (sink->maps); |
| sink->maps = NULL; |
| g_free (sink->messages); |
| sink->messages = NULL; |
| |
| g_free (sink->bind_address); |
| sink->bind_address = NULL; |
| |
| g_mutex_clear (&sink->client_lock); |
| |
| G_OBJECT_CLASS (parent_class)->finalize (object); |
| } |
| |
| /* replacement until we can depend unconditionally on the real one in GLib */ |
| #ifndef HAVE_G_SOCKET_SEND_MESSAGES |
| #define g_socket_send_messages gst_socket_send_messages |
| |
| static gint |
| gst_socket_send_messages (GSocket * socket, GstOutputMessage * messages, |
| guint num_messages, gint flags, GCancellable * cancellable, GError ** error) |
| { |
| gssize result; |
| gint i; |
| |
| for (i = 0; i < num_messages; ++i) { |
| GstOutputMessage *msg = &messages[i]; |
| GError *msg_error = NULL; |
| |
| result = g_socket_send_message (socket, msg->address, |
| msg->vectors, msg->num_vectors, |
| msg->control_messages, msg->num_control_messages, |
| flags, cancellable, &msg_error); |
| |
| if (result < 0) { |
| /* if we couldn't send all messages, just return how many we did |
| * manage to send, provided we managed to send at least one */ |
| if (msg_error->code == G_IO_ERROR_WOULD_BLOCK && i > 0) { |
| g_error_free (msg_error); |
| return i; |
| } else { |
| g_propagate_error (error, msg_error); |
| return -1; |
| } |
| } |
| |
| msg->bytes_sent = result; |
| } |
| |
| return i; |
| } |
| #endif /* HAVE_G_SOCKET_SEND_MESSAGES */ |
| |
| static gsize |
| fill_vectors (GOutputVector * vecs, GstMapInfo * maps, guint n, GstBuffer * buf) |
| { |
| GstMemory *mem; |
| gsize size = 0; |
| guint i; |
| |
| g_assert (gst_buffer_n_memory (buf) == n); |
| |
| for (i = 0; i < n; ++i) { |
| mem = gst_buffer_peek_memory (buf, i); |
| if (gst_memory_map (mem, &maps[i], GST_MAP_READ)) { |
| vecs[i].buffer = maps[i].data; |
| vecs[i].size = maps[i].size; |
| } else { |
| GST_WARNING ("Failed to map memory %p for reading", mem); |
| vecs[i].buffer = ""; |
| vecs[i].size = 0; |
| } |
| size += vecs[i].size; |
| } |
| |
| return size; |
| } |
| |
| static gsize |
| gst_udp_calc_message_size (GstOutputMessage * msg) |
| { |
| gsize size = 0; |
| guint i; |
| |
| for (i = 0; i < msg->num_vectors; ++i) |
| size += msg->vectors[i].size; |
| |
| return size; |
| } |
| |
| static gint |
| gst_udp_messsages_find_first_not_sent (GstOutputMessage * messages, |
| guint num_messages) |
| { |
| guint i; |
| |
| for (i = 0; i < num_messages; ++i) { |
| GstOutputMessage *msg = &messages[i]; |
| |
| if (msg->bytes_sent == 0 && gst_udp_calc_message_size (msg) > 0) |
| return i; |
| } |
| |
| return -1; |
| } |
| |
| static inline gchar * |
| gst_udp_address_get_string (GSocketAddress * addr, gchar * s, gsize size) |
| { |
| GInetSocketAddress *isa = G_INET_SOCKET_ADDRESS (addr); |
| GInetAddress *ia; |
| gchar *addr_str; |
| |
| ia = g_inet_socket_address_get_address (isa); |
| addr_str = g_inet_address_to_string (ia); |
| g_snprintf (s, size, "%s:%u", addr_str, g_inet_socket_address_get_port (isa)); |
| g_free (addr_str); |
| |
| return s; |
| } |
| |
| /* Wrapper around g_socket_send_messages() plus error handling (ignoring). |
| * Returns FALSE if we got cancelled, otherwise TRUE. */ |
| static GstFlowReturn |
| gst_multiudpsink_send_messages (GstMultiUDPSink * sink, GSocket * socket, |
| GstOutputMessage * messages, guint num_messages) |
| { |
| gboolean sent_max_size_warning = FALSE; |
| |
| while (num_messages > 0) { |
| gchar astr[64] G_GNUC_UNUSED; |
| GError *err = NULL; |
| guint msg_size, skip, i; |
| gint ret, err_idx; |
| |
| ret = g_socket_send_messages (socket, messages, num_messages, 0, |
| sink->cancellable, &err); |
| |
| if (G_UNLIKELY (ret < 0)) { |
| GstOutputMessage *msg; |
| |
| if (g_error_matches (err, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { |
| GstFlowReturn flow_ret; |
| |
| g_clear_error (&err); |
| |
| flow_ret = gst_base_sink_wait_preroll (GST_BASE_SINK (sink)); |
| |
| if (flow_ret == GST_FLOW_OK) |
| continue; |
| |
| return flow_ret; |
| } |
| |
| err_idx = gst_udp_messsages_find_first_not_sent (messages, num_messages); |
| if (err_idx < 0) |
| break; |
| |
| msg = &messages[err_idx]; |
| msg_size = gst_udp_calc_message_size (msg); |
| |
| GST_LOG_OBJECT (sink, "error sending %u bytes to client %s: %s", msg_size, |
| gst_udp_address_get_string (msg->address, astr, sizeof (astr)), |
| err->message); |
| |
| skip = 1; |
| if (msg_size > UDP_MAX_SIZE) { |
| if (!sent_max_size_warning) { |
| GST_ELEMENT_WARNING (sink, RESOURCE, WRITE, |
| ("Attempting to send a UDP packets larger than maximum size " |
| "(%u > %d)", msg_size, UDP_MAX_SIZE), |
| ("Reason: %s", err ? err->message : "unknown reason")); |
| sent_max_size_warning = FALSE; |
| } |
| } else { |
| GST_ELEMENT_WARNING (sink, RESOURCE, WRITE, |
| ("Error sending UDP packets"), ("client %s, reason: %s", |
| gst_udp_address_get_string (msg->address, astr, sizeof (astr)), |
| (err != NULL) ? err->message : "unknown reason")); |
| |
| for (i = err_idx + 1; i < num_messages; ++i, ++skip) { |
| if (messages[i].address != msg->address) |
| break; |
| } |
| GST_DEBUG_OBJECT (sink, "skipping %d message(s) to same client", skip); |
| } |
| |
| /* ignore any errors and try sending the rest */ |
| g_clear_error (&err); |
| ret = skip; |
| } |
| |
| g_assert (ret <= num_messages); |
| |
| messages += ret; |
| num_messages -= ret; |
| } |
| |
| return GST_FLOW_OK; |
| } |
| |
| static GstFlowReturn |
| gst_multiudpsink_render_buffers (GstMultiUDPSink * sink, GstBuffer ** buffers, |
| guint num_buffers, guint8 * mem_nums, guint total_mem_num) |
| { |
| GstOutputMessage *msgs; |
| gboolean send_duplicates; |
| GstUDPClient **clients; |
| GOutputVector *vecs; |
| GstMapInfo *map_infos; |
| GstFlowReturn flow_ret; |
| guint num_addr_v4, num_addr_v6; |
| guint num_addr, num_msgs; |
| guint i, j, mem; |
| gsize size = 0; |
| GList *l; |
| |
| send_duplicates = sink->send_duplicates; |
| |
| g_mutex_lock (&sink->client_lock); |
| |
| if (send_duplicates) { |
| num_addr_v4 = sink->num_v4_all; |
| num_addr_v6 = sink->num_v6_all; |
| } else { |
| num_addr_v4 = sink->num_v4_unique; |
| num_addr_v6 = sink->num_v6_unique; |
| } |
| num_addr = num_addr_v4 + num_addr_v6; |
| |
| if (num_addr == 0) |
| goto no_clients; |
| |
| clients = g_newa (GstUDPClient *, num_addr); |
| for (l = sink->clients, i = 0; l != NULL; l = l->next) { |
| GstUDPClient *client = l->data; |
| |
| clients[i++] = gst_udp_client_ref (client); |
| for (j = 1; send_duplicates && j < client->add_count; ++j) |
| clients[i++] = gst_udp_client_ref (client); |
| } |
| g_assert_cmpuint (i, ==, num_addr); |
| |
| g_mutex_unlock (&sink->client_lock); |
| |
| GST_LOG_OBJECT (sink, "%u buffers, %u memories -> to be sent to %u clients", |
| num_buffers, total_mem_num, num_addr); |
| |
| /* ensure our pre-allocated scratch space arrays are large enough */ |
| if (sink->n_vecs < total_mem_num) { |
| sink->n_vecs = GST_ROUND_UP_16 (total_mem_num); |
| g_free (sink->vecs); |
| sink->vecs = g_new (GOutputVector, sink->n_vecs); |
| } |
| vecs = sink->vecs; |
| |
| if (sink->n_maps < total_mem_num) { |
| sink->n_maps = GST_ROUND_UP_16 (total_mem_num); |
| g_free (sink->maps); |
| sink->maps = g_new (GstMapInfo, sink->n_maps); |
| } |
| map_infos = sink->maps; |
| |
| num_msgs = num_addr * num_buffers; |
| if (sink->n_messages < num_msgs) { |
| sink->n_messages = GST_ROUND_UP_16 (num_msgs); |
| g_free (sink->messages); |
| sink->messages = g_new (GstOutputMessage, sink->n_messages); |
| } |
| msgs = sink->messages; |
| |
| /* populate first num_buffers messages with output vectors for the buffers */ |
| for (i = 0, mem = 0; i < num_buffers; ++i) { |
| size += fill_vectors (&vecs[mem], &map_infos[mem], mem_nums[i], buffers[i]); |
| msgs[i].vectors = &vecs[mem]; |
| msgs[i].num_vectors = mem_nums[i]; |
| msgs[i].num_control_messages = 0; |
| msgs[i].bytes_sent = 0; |
| msgs[i].control_messages = NULL; |
| msgs[i].address = clients[0]->addr; |
| mem += mem_nums[i]; |
| } |
| |
| /* FIXME: how about some locking? (there wasn't any before either, but..) */ |
| sink->bytes_to_serve += size; |
| |
| /* now copy the pre-filled num_buffer messages over to the next num_buffer |
| * messages for the next client, where we also change the target adddress */ |
| for (i = 1; i < num_addr; ++i) { |
| for (j = 0; j < num_buffers; ++j) { |
| msgs[i * num_buffers + j] = msgs[j]; |
| msgs[i * num_buffers + j].address = clients[i]->addr; |
| } |
| } |
| |
| /* now send it! */ |
| |
| /* no IPv4 socket? Send it all from the IPv6 socket then.. */ |
| if (sink->used_socket == NULL) { |
| flow_ret = gst_multiudpsink_send_messages (sink, sink->used_socket_v6, |
| msgs, num_msgs); |
| } else { |
| guint num_msgs_v4 = num_buffers * num_addr_v4; |
| guint num_msgs_v6 = num_buffers * num_addr_v6; |
| |
| /* our client list is sorted with IPv4 clients first and IPv6 ones last */ |
| flow_ret = gst_multiudpsink_send_messages (sink, sink->used_socket, |
| msgs, num_msgs_v4); |
| |
| if (flow_ret != GST_FLOW_OK) |
| goto cancelled; |
| |
| flow_ret = gst_multiudpsink_send_messages (sink, sink->used_socket_v6, |
| msgs + num_msgs_v4, num_msgs_v6); |
| } |
| |
| if (flow_ret != GST_FLOW_OK) |
| goto cancelled; |
| |
| /* now update stats */ |
| g_mutex_lock (&sink->client_lock); |
| |
| for (i = 0; i < num_addr; ++i) { |
| GstUDPClient *client = clients[i]; |
| |
| for (j = 0; j < num_buffers; ++j) { |
| gsize bytes_sent; |
| |
| bytes_sent = msgs[i * num_buffers + j].bytes_sent; |
| |
| client->bytes_sent += bytes_sent; |
| client->packets_sent++; |
| sink->bytes_served += bytes_sent; |
| } |
| gst_udp_client_unref (client); |
| } |
| |
| g_mutex_unlock (&sink->client_lock); |
| |
| out: |
| |
| for (i = 0; i < mem; ++i) |
| gst_memory_unmap (map_infos[i].memory, &map_infos[i]); |
| |
| return flow_ret; |
| |
| no_clients: |
| { |
| g_mutex_unlock (&sink->client_lock); |
| GST_LOG_OBJECT (sink, "no clients"); |
| return GST_FLOW_OK; |
| } |
| cancelled: |
| { |
| GST_INFO_OBJECT (sink, "cancelled"); |
| |
| g_mutex_lock (&sink->client_lock); |
| for (i = 0; i < num_addr; ++i) |
| gst_udp_client_unref (clients[i]); |
| g_mutex_unlock (&sink->client_lock); |
| goto out; |
| } |
| } |
| |
| static GstFlowReturn |
| gst_multiudpsink_render_list (GstBaseSink * bsink, GstBufferList * buffer_list) |
| { |
| GstMultiUDPSink *sink; |
| GstBuffer **buffers; |
| GstFlowReturn flow; |
| guint8 *mem_nums; |
| guint total_mems; |
| guint i, num_buffers; |
| |
| sink = GST_MULTIUDPSINK_CAST (bsink); |
| |
| num_buffers = gst_buffer_list_length (buffer_list); |
| if (num_buffers == 0) |
| goto no_data; |
| |
| buffers = g_newa (GstBuffer *, num_buffers); |
| mem_nums = g_newa (guint8, num_buffers); |
| for (i = 0, total_mems = 0; i < num_buffers; ++i) { |
| buffers[i] = gst_buffer_list_get (buffer_list, i); |
| mem_nums[i] = gst_buffer_n_memory (buffers[i]); |
| total_mems += mem_nums[i]; |
| } |
| |
| flow = gst_multiudpsink_render_buffers (sink, buffers, num_buffers, |
| mem_nums, total_mems); |
| |
| return flow; |
| |
| no_data: |
| { |
| GST_LOG_OBJECT (sink, "empty buffer"); |
| return GST_FLOW_OK; |
| } |
| } |
| |
| static GstFlowReturn |
| gst_multiudpsink_render (GstBaseSink * bsink, GstBuffer * buffer) |
| { |
| GstMultiUDPSink *sink; |
| GstFlowReturn flow; |
| guint8 n_mem; |
| |
| sink = GST_MULTIUDPSINK_CAST (bsink); |
| |
| n_mem = gst_buffer_n_memory (buffer); |
| |
| if (n_mem > 0) |
| flow = gst_multiudpsink_render_buffers (sink, &buffer, 1, &n_mem, n_mem); |
| else |
| flow = GST_FLOW_OK; |
| |
| return flow; |
| } |
| |
| static void |
| gst_multiudpsink_set_clients_string (GstMultiUDPSink * sink, |
| const gchar * string) |
| { |
| gchar **clients; |
| gint i; |
| |
| clients = g_strsplit (string, ",", 0); |
| |
| g_mutex_lock (&sink->client_lock); |
| /* clear all existing clients */ |
| gst_multiudpsink_clear_internal (sink, FALSE); |
| for (i = 0; clients[i]; i++) { |
| gchar *host, *p; |
| gint64 port = 0; |
| |
| host = clients[i]; |
| p = strstr (clients[i], ":"); |
| if (p != NULL) { |
| *p = '\0'; |
| port = g_ascii_strtoll (p + 1, NULL, 10); |
| } |
| if (port != 0) |
| gst_multiudpsink_add_internal (sink, host, port, FALSE); |
| } |
| g_mutex_unlock (&sink->client_lock); |
| |
| g_strfreev (clients); |
| } |
| |
| static gchar * |
| gst_multiudpsink_get_clients_string (GstMultiUDPSink * sink) |
| { |
| GString *str; |
| GList *clients; |
| |
| str = g_string_new (""); |
| |
| g_mutex_lock (&sink->client_lock); |
| clients = sink->clients; |
| while (clients) { |
| GstUDPClient *client; |
| gint count; |
| |
| client = (GstUDPClient *) clients->data; |
| |
| clients = g_list_next (clients); |
| |
| count = client->add_count; |
| while (count--) { |
| g_string_append_printf (str, "%s:%d%s", client->host, client->port, |
| (clients || count > 1 ? "," : "")); |
| } |
| } |
| g_mutex_unlock (&sink->client_lock); |
| |
| return g_string_free (str, FALSE); |
| } |
| |
| static void |
| gst_multiudpsink_setup_qos_dscp (GstMultiUDPSink * sink, GSocket * socket) |
| { |
| /* don't touch on -1 */ |
| if (sink->qos_dscp < 0) |
| return; |
| |
| if (socket == NULL) |
| return; |
| |
| #ifdef IP_TOS |
| { |
| gint tos; |
| gint fd; |
| |
| fd = g_socket_get_fd (socket); |
| |
| GST_DEBUG_OBJECT (sink, "setting TOS to %d", sink->qos_dscp); |
| |
| /* Extract and shift 6 bits of DSFIELD */ |
| tos = (sink->qos_dscp & 0x3f) << 2; |
| |
| if (setsockopt (fd, IPPROTO_IP, IP_TOS, &tos, sizeof (tos)) < 0) { |
| GST_ERROR_OBJECT (sink, "could not set TOS: %s", g_strerror (errno)); |
| } |
| #ifdef IPV6_TCLASS |
| if (g_socket_get_family (socket) == G_SOCKET_FAMILY_IPV6) { |
| if (setsockopt (fd, IPPROTO_IPV6, IPV6_TCLASS, &tos, sizeof (tos)) < 0) { |
| GST_ERROR_OBJECT (sink, "could not set TCLASS: %s", g_strerror (errno)); |
| } |
| } |
| #endif |
| } |
| #endif |
| } |
| |
| static void |
| gst_multiudpsink_set_property (GObject * object, guint prop_id, |
| const GValue * value, GParamSpec * pspec) |
| { |
| GstMultiUDPSink *udpsink; |
| |
| udpsink = GST_MULTIUDPSINK (object); |
| |
| switch (prop_id) { |
| case PROP_SOCKET: |
| if (udpsink->socket != NULL && udpsink->socket != udpsink->used_socket && |
| udpsink->close_socket) { |
| GError *err = NULL; |
| |
| if (!g_socket_close (udpsink->socket, &err)) { |
| GST_ERROR ("failed to close socket %p: %s", udpsink->socket, |
| err->message); |
| g_clear_error (&err); |
| } |
| } |
| if (udpsink->socket) |
| g_object_unref (udpsink->socket); |
| udpsink->socket = g_value_dup_object (value); |
| GST_DEBUG_OBJECT (udpsink, "setting socket to %p", udpsink->socket); |
| break; |
| case PROP_SOCKET_V6: |
| if (udpsink->socket_v6 != NULL |
| && udpsink->socket_v6 != udpsink->used_socket_v6 |
| && udpsink->close_socket) { |
| GError *err = NULL; |
| |
| if (!g_socket_close (udpsink->socket_v6, &err)) { |
| GST_ERROR ("failed to close socket %p: %s", udpsink->socket_v6, |
| err->message); |
| g_clear_error (&err); |
| } |
| } |
| if (udpsink->socket_v6) |
| g_object_unref (udpsink->socket_v6); |
| udpsink->socket_v6 = g_value_dup_object (value); |
| GST_DEBUG_OBJECT (udpsink, "setting socket to %p", udpsink->socket_v6); |
| break; |
| case PROP_CLOSE_SOCKET: |
| udpsink->close_socket = g_value_get_boolean (value); |
| break; |
| case PROP_CLIENTS: |
| gst_multiudpsink_set_clients_string (udpsink, g_value_get_string (value)); |
| break; |
| case PROP_AUTO_MULTICAST: |
| udpsink->auto_multicast = g_value_get_boolean (value); |
| break; |
| case PROP_MULTICAST_IFACE: |
| g_free (udpsink->multi_iface); |
| |
| if (g_value_get_string (value) == NULL) |
| udpsink->multi_iface = g_strdup (DEFAULT_MULTICAST_IFACE); |
| else |
| udpsink->multi_iface = g_value_dup_string (value); |
| break; |
| case PROP_TTL: |
| udpsink->ttl = g_value_get_int (value); |
| break; |
| case PROP_TTL_MC: |
| udpsink->ttl_mc = g_value_get_int (value); |
| break; |
| case PROP_LOOP: |
| udpsink->loop = g_value_get_boolean (value); |
| break; |
| case PROP_FORCE_IPV4: |
| udpsink->force_ipv4 = g_value_get_boolean (value); |
| break; |
| case PROP_QOS_DSCP: |
| udpsink->qos_dscp = g_value_get_int (value); |
| gst_multiudpsink_setup_qos_dscp (udpsink, udpsink->used_socket); |
| gst_multiudpsink_setup_qos_dscp (udpsink, udpsink->used_socket_v6); |
| break; |
| case PROP_SEND_DUPLICATES: |
| udpsink->send_duplicates = g_value_get_boolean (value); |
| break; |
| case PROP_BUFFER_SIZE: |
| udpsink->buffer_size = g_value_get_int (value); |
| break; |
| case PROP_BIND_ADDRESS: |
| g_free (udpsink->bind_address); |
| udpsink->bind_address = g_value_dup_string (value); |
| break; |
| case PROP_BIND_PORT: |
| udpsink->bind_port = g_value_get_int (value); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static void |
| gst_multiudpsink_get_property (GObject * object, guint prop_id, GValue * value, |
| GParamSpec * pspec) |
| { |
| GstMultiUDPSink *udpsink; |
| |
| udpsink = GST_MULTIUDPSINK (object); |
| |
| switch (prop_id) { |
| case PROP_BYTES_TO_SERVE: |
| g_value_set_uint64 (value, udpsink->bytes_to_serve); |
| break; |
| case PROP_BYTES_SERVED: |
| g_value_set_uint64 (value, udpsink->bytes_served); |
| break; |
| case PROP_SOCKET: |
| g_value_set_object (value, udpsink->socket); |
| break; |
| case PROP_SOCKET_V6: |
| g_value_set_object (value, udpsink->socket_v6); |
| break; |
| case PROP_CLOSE_SOCKET: |
| g_value_set_boolean (value, udpsink->close_socket); |
| break; |
| case PROP_USED_SOCKET: |
| g_value_set_object (value, udpsink->used_socket); |
| break; |
| case PROP_USED_SOCKET_V6: |
| g_value_set_object (value, udpsink->used_socket_v6); |
| break; |
| case PROP_CLIENTS: |
| g_value_take_string (value, |
| gst_multiudpsink_get_clients_string (udpsink)); |
| break; |
| case PROP_AUTO_MULTICAST: |
| g_value_set_boolean (value, udpsink->auto_multicast); |
| break; |
| case PROP_MULTICAST_IFACE: |
| g_value_set_string (value, udpsink->multi_iface); |
| break; |
| case PROP_TTL: |
| g_value_set_int (value, udpsink->ttl); |
| break; |
| case PROP_TTL_MC: |
| g_value_set_int (value, udpsink->ttl_mc); |
| break; |
| case PROP_LOOP: |
| g_value_set_boolean (value, udpsink->loop); |
| break; |
| case PROP_FORCE_IPV4: |
| g_value_set_boolean (value, udpsink->force_ipv4); |
| break; |
| case PROP_QOS_DSCP: |
| g_value_set_int (value, udpsink->qos_dscp); |
| break; |
| case PROP_SEND_DUPLICATES: |
| g_value_set_boolean (value, udpsink->send_duplicates); |
| break; |
| case PROP_BUFFER_SIZE: |
| g_value_set_int (value, udpsink->buffer_size); |
| break; |
| case PROP_BIND_ADDRESS: |
| g_value_set_string (value, udpsink->bind_address); |
| break; |
| case PROP_BIND_PORT: |
| g_value_set_int (value, udpsink->bind_port); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static gboolean |
| gst_multiudpsink_configure_client (GstMultiUDPSink * sink, |
| GstUDPClient * client) |
| { |
| GInetSocketAddress *saddr = G_INET_SOCKET_ADDRESS (client->addr); |
| GInetAddress *addr = g_inet_socket_address_get_address (saddr); |
| GSocketFamily family = g_socket_address_get_family (G_SOCKET_ADDRESS (saddr)); |
| GSocket *socket; |
| GError *err = NULL; |
| |
| GST_DEBUG_OBJECT (sink, "configuring client %p", client); |
| |
| if (family == G_SOCKET_FAMILY_IPV6 && !sink->used_socket_v6) |
| goto invalid_family; |
| |
| /* Select socket to send from for this address */ |
| if (family == G_SOCKET_FAMILY_IPV6 || !sink->used_socket) |
| socket = sink->used_socket_v6; |
| else |
| socket = sink->used_socket; |
| |
| if (g_inet_address_get_is_multicast (addr)) { |
| GST_DEBUG_OBJECT (sink, "we have a multicast client %p", client); |
| if (sink->auto_multicast) { |
| GST_DEBUG_OBJECT (sink, "autojoining group"); |
| if (!g_socket_join_multicast_group (socket, addr, FALSE, |
| sink->multi_iface, &err)) |
| goto join_group_failed; |
| } |
| GST_DEBUG_OBJECT (sink, "setting loop to %d", sink->loop); |
| g_socket_set_multicast_loopback (socket, sink->loop); |
| GST_DEBUG_OBJECT (sink, "setting ttl to %d", sink->ttl_mc); |
| g_socket_set_multicast_ttl (socket, sink->ttl_mc); |
| } else { |
| GST_DEBUG_OBJECT (sink, "setting unicast ttl to %d", sink->ttl); |
| g_socket_set_ttl (socket, sink->ttl); |
| } |
| return TRUE; |
| |
| /* ERRORS */ |
| join_group_failed: |
| { |
| gst_multiudpsink_stop (GST_BASE_SINK (sink)); |
| GST_ELEMENT_ERROR (sink, RESOURCE, SETTINGS, (NULL), |
| ("Could not join multicast group: %s", |
| err ? err->message : "unknown reason")); |
| g_clear_error (&err); |
| return FALSE; |
| } |
| invalid_family: |
| { |
| gst_multiudpsink_stop (GST_BASE_SINK (sink)); |
| GST_ELEMENT_ERROR (sink, RESOURCE, SETTINGS, (NULL), |
| ("Invalid address family (got %d)", family)); |
| return FALSE; |
| } |
| } |
| |
| /* create a socket for sending to remote machine */ |
| static gboolean |
| gst_multiudpsink_start (GstBaseSink * bsink) |
| { |
| GstMultiUDPSink *sink; |
| GList *clients; |
| GstUDPClient *client; |
| GError *err = NULL; |
| |
| sink = GST_MULTIUDPSINK (bsink); |
| |
| sink->external_socket = FALSE; |
| |
| if (sink->socket) { |
| GST_DEBUG_OBJECT (sink, "using configured socket"); |
| if (g_socket_get_family (sink->socket) == G_SOCKET_FAMILY_IPV6) { |
| sink->used_socket_v6 = G_SOCKET (g_object_ref (sink->socket)); |
| sink->external_socket = TRUE; |
| } else { |
| sink->used_socket = G_SOCKET (g_object_ref (sink->socket)); |
| sink->external_socket = TRUE; |
| } |
| } |
| |
| if (sink->socket_v6) { |
| GST_DEBUG_OBJECT (sink, "using configured IPv6 socket"); |
| g_return_val_if_fail (!sink->socket || g_socket_get_family (sink->socket) != |
| G_SOCKET_FAMILY_IPV6, FALSE); |
| |
| if (sink->used_socket_v6 && sink->used_socket_v6 != sink->socket_v6) { |
| GST_ERROR_OBJECT (sink, |
| "Provided different IPv6 sockets in socket and socket-v6 properties"); |
| return FALSE; |
| } |
| |
| sink->used_socket_v6 = G_SOCKET (g_object_ref (sink->socket_v6)); |
| sink->external_socket = TRUE; |
| } |
| |
| if (!sink->used_socket && !sink->used_socket_v6) { |
| GSocketAddress *bind_addr; |
| GInetAddress *bind_iaddr; |
| |
| if (sink->bind_address) { |
| GSocketFamily family; |
| |
| bind_iaddr = g_inet_address_new_from_string (sink->bind_address); |
| if (!bind_iaddr) { |
| GList *results; |
| GResolver *resolver; |
| |
| resolver = g_resolver_get_default (); |
| results = |
| g_resolver_lookup_by_name (resolver, sink->bind_address, |
| sink->cancellable, &err); |
| if (!results) { |
| g_object_unref (resolver); |
| goto name_resolve; |
| } |
| bind_iaddr = G_INET_ADDRESS (g_object_ref (results->data)); |
| g_resolver_free_addresses (results); |
| g_object_unref (resolver); |
| } |
| |
| bind_addr = g_inet_socket_address_new (bind_iaddr, sink->bind_port); |
| g_object_unref (bind_iaddr); |
| family = g_socket_address_get_family (G_SOCKET_ADDRESS (bind_addr)); |
| |
| if ((sink->used_socket = |
| g_socket_new (family, G_SOCKET_TYPE_DATAGRAM, |
| G_SOCKET_PROTOCOL_UDP, &err)) == NULL) { |
| g_object_unref (bind_addr); |
| goto no_socket; |
| } |
| |
| g_socket_bind (sink->used_socket, bind_addr, TRUE, &err); |
| g_object_unref (bind_addr); |
| if (err != NULL) |
| goto bind_error; |
| } else { |
| /* create sender sockets if none available */ |
| if ((sink->used_socket = g_socket_new (G_SOCKET_FAMILY_IPV4, |
| G_SOCKET_TYPE_DATAGRAM, G_SOCKET_PROTOCOL_UDP, &err)) == NULL) |
| goto no_socket; |
| |
| bind_iaddr = g_inet_address_new_any (G_SOCKET_FAMILY_IPV4); |
| bind_addr = g_inet_socket_address_new (bind_iaddr, sink->bind_port); |
| g_socket_bind (sink->used_socket, bind_addr, TRUE, &err); |
| g_object_unref (bind_addr); |
| g_object_unref (bind_iaddr); |
| if (err != NULL) |
| goto bind_error; |
| |
| if ((sink->used_socket_v6 = g_socket_new (G_SOCKET_FAMILY_IPV6, |
| G_SOCKET_TYPE_DATAGRAM, G_SOCKET_PROTOCOL_UDP, |
| &err)) == NULL) { |
| GST_INFO_OBJECT (sink, "Failed to create IPv6 socket: %s", |
| err->message); |
| g_clear_error (&err); |
| } else { |
| bind_iaddr = g_inet_address_new_any (G_SOCKET_FAMILY_IPV6); |
| bind_addr = g_inet_socket_address_new (bind_iaddr, sink->bind_port); |
| g_socket_bind (sink->used_socket_v6, bind_addr, TRUE, &err); |
| g_object_unref (bind_addr); |
| g_object_unref (bind_iaddr); |
| if (err != NULL) |
| goto bind_error; |
| } |
| } |
| } |
| #ifdef SO_SNDBUF |
| { |
| socklen_t len; |
| gint sndsize, ret; |
| |
| len = sizeof (sndsize); |
| if (sink->buffer_size != 0) { |
| sndsize = sink->buffer_size; |
| |
| GST_DEBUG_OBJECT (sink, "setting udp buffer of %d bytes", sndsize); |
| /* set buffer size, Note that on Linux this is typically limited to a |
| * maximum of around 100K. Also a minimum of 128 bytes is required on |
| * Linux. */ |
| |
| if (sink->used_socket) { |
| ret = |
| setsockopt (g_socket_get_fd (sink->used_socket), SOL_SOCKET, |
| SO_SNDBUF, (void *) &sndsize, len); |
| if (ret != 0) { |
| GST_ELEMENT_WARNING (sink, RESOURCE, SETTINGS, (NULL), |
| ("Could not create a buffer of requested %d bytes, %d: %s", |
| sndsize, ret, g_strerror (errno))); |
| } |
| } |
| |
| if (sink->used_socket_v6) { |
| ret = |
| setsockopt (g_socket_get_fd (sink->used_socket_v6), SOL_SOCKET, |
| SO_SNDBUF, (void *) &sndsize, len); |
| if (ret != 0) { |
| GST_ELEMENT_WARNING (sink, RESOURCE, SETTINGS, (NULL), |
| ("Could not create a buffer of requested %d bytes, %d: %s", |
| sndsize, ret, g_strerror (errno))); |
| } |
| } |
| } |
| |
| /* read the value of the receive buffer. Note that on linux this returns 2x the |
| * value we set because the kernel allocates extra memory for metadata. |
| * The default on Linux is about 100K (which is about 50K without metadata) */ |
| if (sink->used_socket) { |
| ret = |
| getsockopt (g_socket_get_fd (sink->used_socket), SOL_SOCKET, |
| SO_SNDBUF, (void *) &sndsize, &len); |
| if (ret == 0) |
| GST_DEBUG_OBJECT (sink, "have UDP buffer of %d bytes", sndsize); |
| else |
| GST_DEBUG_OBJECT (sink, "could not get UDP buffer size"); |
| } |
| |
| if (sink->used_socket_v6) { |
| ret = |
| getsockopt (g_socket_get_fd (sink->used_socket_v6), SOL_SOCKET, |
| SO_SNDBUF, (void *) &sndsize, &len); |
| if (ret == 0) |
| GST_DEBUG_OBJECT (sink, "have UDPv6 buffer of %d bytes", sndsize); |
| else |
| GST_DEBUG_OBJECT (sink, "could not get UDPv6 buffer size"); |
| } |
| } |
| #endif |
| |
| #ifdef SO_BINDTODEVICE |
| if (sink->multi_iface) { |
| if (sink->used_socket) { |
| if (setsockopt (g_socket_get_fd (sink->used_socket), SOL_SOCKET, |
| SO_BINDTODEVICE, sink->multi_iface, |
| strlen (sink->multi_iface)) < 0) |
| GST_WARNING_OBJECT (sink, "setsockopt SO_BINDTODEVICE failed: %s", |
| strerror (errno)); |
| } |
| if (sink->used_socket_v6) { |
| if (setsockopt (g_socket_get_fd (sink->used_socket_v6), SOL_SOCKET, |
| SO_BINDTODEVICE, sink->multi_iface, |
| strlen (sink->multi_iface)) < 0) |
| GST_WARNING_OBJECT (sink, "setsockopt SO_BINDTODEVICE failed (v6): %s", |
| strerror (errno)); |
| } |
| } |
| #endif |
| |
| if (sink->used_socket) |
| g_socket_set_broadcast (sink->used_socket, TRUE); |
| if (sink->used_socket_v6) |
| g_socket_set_broadcast (sink->used_socket_v6, TRUE); |
| |
| sink->bytes_to_serve = 0; |
| sink->bytes_served = 0; |
| |
| gst_multiudpsink_setup_qos_dscp (sink, sink->used_socket); |
| gst_multiudpsink_setup_qos_dscp (sink, sink->used_socket_v6); |
| |
| /* look for multicast clients and join multicast groups appropriately |
| set also ttl and multicast loopback delivery appropriately */ |
| for (clients = sink->clients; clients; clients = g_list_next (clients)) { |
| client = (GstUDPClient *) clients->data; |
| |
| if (!gst_multiudpsink_configure_client (sink, client)) |
| return FALSE; |
| } |
| return TRUE; |
| |
| /* ERRORS */ |
| no_socket: |
| { |
| GST_ELEMENT_ERROR (sink, RESOURCE, FAILED, (NULL), |
| ("Could not create socket: %s", err->message)); |
| g_clear_error (&err); |
| return FALSE; |
| } |
| bind_error: |
| { |
| GST_ELEMENT_ERROR (sink, RESOURCE, FAILED, (NULL), |
| ("Failed to bind socket: %s", err->message)); |
| g_clear_error (&err); |
| return FALSE; |
| } |
| name_resolve: |
| { |
| GST_ELEMENT_ERROR (sink, RESOURCE, FAILED, (NULL), |
| ("Failed to resolve bind address %s: %s", sink->bind_address, |
| err->message)); |
| g_clear_error (&err); |
| return FALSE; |
| } |
| } |
| |
| static gboolean |
| gst_multiudpsink_stop (GstBaseSink * bsink) |
| { |
| GstMultiUDPSink *udpsink; |
| |
| udpsink = GST_MULTIUDPSINK (bsink); |
| |
| if (udpsink->used_socket) { |
| if (udpsink->close_socket || !udpsink->external_socket) { |
| GError *err = NULL; |
| |
| if (!g_socket_close (udpsink->used_socket, &err)) { |
| GST_ERROR_OBJECT (udpsink, "Failed to close socket: %s", err->message); |
| g_clear_error (&err); |
| } |
| } |
| |
| g_object_unref (udpsink->used_socket); |
| udpsink->used_socket = NULL; |
| } |
| |
| if (udpsink->used_socket_v6) { |
| if (udpsink->close_socket || !udpsink->external_socket) { |
| GError *err = NULL; |
| |
| if (!g_socket_close (udpsink->used_socket_v6, &err)) { |
| GST_ERROR_OBJECT (udpsink, "Failed to close socket: %s", err->message); |
| g_clear_error (&err); |
| } |
| } |
| |
| g_object_unref (udpsink->used_socket_v6); |
| udpsink->used_socket_v6 = NULL; |
| } |
| |
| return TRUE; |
| } |
| |
| static gint |
| gst_udp_client_compare_socket_family (GstUDPClient * a, GstUDPClient * b) |
| { |
| GSocketFamily fa = g_socket_address_get_family (a->addr); |
| GSocketFamily fb = g_socket_address_get_family (b->addr); |
| |
| if (fa == fb) |
| return 0; |
| |
| /* a should go before b */ |
| if (fa == G_SOCKET_FAMILY_IPV4 && fb == G_SOCKET_FAMILY_IPV6) |
| return -1; |
| |
| /* b should go before a */ |
| return 1; |
| } |
| |
| static void |
| gst_multiudpsink_add_internal (GstMultiUDPSink * sink, const gchar * host, |
| gint port, gboolean lock) |
| { |
| GSocketFamily family; |
| GstUDPClient *client; |
| GstUDPClient udpclient; |
| GTimeVal now; |
| GList *find; |
| |
| udpclient.host = (gchar *) host; |
| udpclient.port = port; |
| |
| GST_DEBUG_OBJECT (sink, "adding client on host %s, port %d", host, port); |
| |
| if (lock) |
| g_mutex_lock (&sink->client_lock); |
| |
| find = g_list_find_custom (sink->clients, &udpclient, |
| (GCompareFunc) client_compare); |
| |
| if (!find) { |
| find = g_list_find_custom (sink->clients_to_be_removed, &udpclient, |
| (GCompareFunc) client_compare); |
| if (find) |
| gst_udp_client_ref (find->data); |
| } |
| |
| if (find) { |
| client = (GstUDPClient *) find->data; |
| |
| family = g_socket_address_get_family (client->addr); |
| |
| GST_DEBUG_OBJECT (sink, "found %d existing clients with host %s, port %d", |
| client->add_count, host, port); |
| } else { |
| client = gst_udp_client_new (sink, host, port); |
| if (!client) |
| goto error; |
| |
| family = g_socket_address_get_family (client->addr); |
| |
| g_get_current_time (&now); |
| client->connect_time = GST_TIMEVAL_TO_TIME (now); |
| |
| if (sink->used_socket) |
| gst_multiudpsink_configure_client (sink, client); |
| |
| GST_DEBUG_OBJECT (sink, "add client with host %s, port %d", host, port); |
| |
| /* keep IPv4 clients at the beginning, and IPv6 at the end, we can make |
| * use of this in gst_multiudpsink_render_buffers() */ |
| sink->clients = g_list_insert_sorted (sink->clients, client, |
| (GCompareFunc) gst_udp_client_compare_socket_family); |
| |
| if (family == G_SOCKET_FAMILY_IPV4) |
| ++sink->num_v4_unique; |
| else |
| ++sink->num_v6_unique; |
| } |
| |
| ++client->add_count; |
| |
| if (family == G_SOCKET_FAMILY_IPV4) |
| ++sink->num_v4_all; |
| else |
| ++sink->num_v6_all; |
| |
| if (lock) |
| g_mutex_unlock (&sink->client_lock); |
| |
| g_signal_emit (G_OBJECT (sink), |
| gst_multiudpsink_signals[SIGNAL_CLIENT_ADDED], 0, host, port); |
| |
| GST_DEBUG_OBJECT (sink, "added client on host %s, port %d", host, port); |
| return; |
| |
| /* ERRORS */ |
| error: |
| { |
| GST_DEBUG_OBJECT (sink, "did not add client on host %s, port %d", host, |
| port); |
| if (lock) |
| g_mutex_unlock (&sink->client_lock); |
| return; |
| } |
| } |
| |
| void |
| gst_multiudpsink_add (GstMultiUDPSink * sink, const gchar * host, gint port) |
| { |
| gst_multiudpsink_add_internal (sink, host, port, TRUE); |
| } |
| |
| void |
| gst_multiudpsink_remove (GstMultiUDPSink * sink, const gchar * host, gint port) |
| { |
| GSocketFamily family; |
| GList *find; |
| GstUDPClient udpclient; |
| GstUDPClient *client; |
| GTimeVal now; |
| |
| udpclient.host = (gchar *) host; |
| udpclient.port = port; |
| |
| g_mutex_lock (&sink->client_lock); |
| find = g_list_find_custom (sink->clients, &udpclient, |
| (GCompareFunc) client_compare); |
| if (!find) |
| goto not_found; |
| |
| client = (GstUDPClient *) find->data; |
| |
| GST_DEBUG_OBJECT (sink, "found %d clients with host %s, port %d", |
| client->add_count, host, port); |
| |
| --client->add_count; |
| |
| family = g_socket_address_get_family (client->addr); |
| if (family == G_SOCKET_FAMILY_IPV4) |
| --sink->num_v4_all; |
| else |
| --sink->num_v6_all; |
| |
| if (client->add_count == 0) { |
| GInetSocketAddress *saddr = G_INET_SOCKET_ADDRESS (client->addr); |
| GInetAddress *addr = g_inet_socket_address_get_address (saddr); |
| GSocket *socket; |
| |
| /* Select socket to send from for this address */ |
| if (family == G_SOCKET_FAMILY_IPV6 || !sink->used_socket) |
| socket = sink->used_socket_v6; |
| else |
| socket = sink->used_socket; |
| |
| GST_DEBUG_OBJECT (sink, "remove client with host %s, port %d", host, port); |
| |
| g_get_current_time (&now); |
| client->disconnect_time = GST_TIMEVAL_TO_TIME (now); |
| |
| if (socket && sink->auto_multicast |
| && g_inet_address_get_is_multicast (addr)) { |
| GError *err = NULL; |
| |
| if (!g_socket_leave_multicast_group (socket, addr, FALSE, |
| sink->multi_iface, &err)) { |
| GST_DEBUG_OBJECT (sink, "Failed to leave multicast group: %s", |
| err->message); |
| g_clear_error (&err); |
| } |
| } |
| |
| if (family == G_SOCKET_FAMILY_IPV4) |
| --sink->num_v4_unique; |
| else |
| --sink->num_v6_unique; |
| |
| /* Keep state consistent for streaming thread, so remove from client list, |
| * but keep it around until after the signal has been emitted, in case a |
| * callback wants to get stats for that client or so */ |
| sink->clients = g_list_delete_link (sink->clients, find); |
| |
| sink->clients_to_be_removed = |
| g_list_prepend (sink->clients_to_be_removed, client); |
| |
| /* Unlock to emit signal before we delete the actual client */ |
| g_mutex_unlock (&sink->client_lock); |
| g_signal_emit (G_OBJECT (sink), |
| gst_multiudpsink_signals[SIGNAL_CLIENT_REMOVED], 0, host, port); |
| g_mutex_lock (&sink->client_lock); |
| |
| sink->clients_to_be_removed = |
| g_list_remove (sink->clients_to_be_removed, client); |
| |
| gst_udp_client_unref (client); |
| } |
| g_mutex_unlock (&sink->client_lock); |
| |
| return; |
| |
| /* ERRORS */ |
| not_found: |
| { |
| g_mutex_unlock (&sink->client_lock); |
| GST_WARNING_OBJECT (sink, "client at host %s, port %d not found", |
| host, port); |
| return; |
| } |
| } |
| |
| static void |
| gst_multiudpsink_clear_internal (GstMultiUDPSink * sink, gboolean lock) |
| { |
| GST_DEBUG_OBJECT (sink, "clearing"); |
| /* we only need to remove the client structure, there is no additional |
| * socket or anything to free for UDP */ |
| if (lock) |
| g_mutex_lock (&sink->client_lock); |
| g_list_foreach (sink->clients, (GFunc) gst_udp_client_unref, sink); |
| g_list_free (sink->clients); |
| sink->clients = NULL; |
| sink->num_v4_unique = 0; |
| sink->num_v4_all = 0; |
| sink->num_v6_unique = 0; |
| sink->num_v6_all = 0; |
| if (lock) |
| g_mutex_unlock (&sink->client_lock); |
| } |
| |
| void |
| gst_multiudpsink_clear (GstMultiUDPSink * sink) |
| { |
| gst_multiudpsink_clear_internal (sink, TRUE); |
| } |
| |
| GstStructure * |
| gst_multiudpsink_get_stats (GstMultiUDPSink * sink, const gchar * host, |
| gint port) |
| { |
| GstUDPClient *client; |
| GstStructure *result = NULL; |
| GstUDPClient udpclient; |
| GList *find; |
| |
| udpclient.host = (gchar *) host; |
| udpclient.port = port; |
| |
| g_mutex_lock (&sink->client_lock); |
| |
| find = g_list_find_custom (sink->clients, &udpclient, |
| (GCompareFunc) client_compare); |
| |
| if (!find) |
| find = g_list_find_custom (sink->clients_to_be_removed, &udpclient, |
| (GCompareFunc) client_compare); |
| |
| if (!find) |
| goto not_found; |
| |
| GST_DEBUG_OBJECT (sink, "stats for client with host %s, port %d", host, port); |
| |
| client = (GstUDPClient *) find->data; |
| |
| result = gst_structure_new_empty ("multiudpsink-stats"); |
| |
| gst_structure_set (result, |
| "bytes-sent", G_TYPE_UINT64, client->bytes_sent, |
| "packets-sent", G_TYPE_UINT64, client->packets_sent, |
| "connect-time", G_TYPE_UINT64, client->connect_time, |
| "disconnect-time", G_TYPE_UINT64, client->disconnect_time, NULL); |
| |
| g_mutex_unlock (&sink->client_lock); |
| |
| return result; |
| |
| /* ERRORS */ |
| not_found: |
| { |
| g_mutex_unlock (&sink->client_lock); |
| GST_WARNING_OBJECT (sink, "client with host %s, port %d not found", |
| host, port); |
| /* Apparently (see comment in gstmultifdsink.c) returning NULL from here may |
| * confuse/break python bindings */ |
| return gst_structure_new_empty ("multiudpsink-stats"); |
| } |
| } |
| |
| static gboolean |
| gst_multiudpsink_unlock (GstBaseSink * bsink) |
| { |
| GstMultiUDPSink *sink; |
| |
| sink = GST_MULTIUDPSINK (bsink); |
| |
| g_cancellable_cancel (sink->cancellable); |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| gst_multiudpsink_unlock_stop (GstBaseSink * bsink) |
| { |
| GstMultiUDPSink *sink; |
| |
| sink = GST_MULTIUDPSINK (bsink); |
| |
| gst_multiudpsink_free_cancellable (sink); |
| gst_multiudpsink_create_cancellable (sink); |
| |
| return TRUE; |
| } |