| /* GStreamer | |
| * Copyright (C) 2018, Collabora Ltd. | |
| * Copyright (C) 2018, SK Telecom, Co., Ltd. | |
| * Author: Jeongseok Kim <jeongseok.kim@sk.com> | |
| * | |
| * 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. | |
| */ | |
| #ifdef HAVE_CONFIG_H | |
| #include "config.h" | |
| #endif | |
| /* Needed for GValueArray */ | |
| #define GLIB_DISABLE_DEPRECATION_WARNINGS | |
| #include "gstsrtobject.h" | |
| #include <gst/base/gstbasesink.h> | |
| #include <gio/gnetworking.h> | |
| #include <stdlib.h> | |
| #include <stdbool.h> | |
| #include <stdint.h> | |
| GST_DEBUG_CATEGORY_EXTERN (gst_debug_srtobject); | |
| #define GST_CAT_DEFAULT gst_debug_srtobject | |
| #define ERROR_TO_WARNING(srtobject, error, suffix) \ | |
| G_STMT_START { \ | |
| gchar *text; \ | |
| g_assert (error); \ | |
| text = g_strdup_printf ("%s%s", (error)->message, (suffix)); \ | |
| GST_WARNING_OBJECT ((srtobject)->element, "warning: %s", text); \ | |
| gst_element_message_full ((srtobject)->element, GST_MESSAGE_WARNING, \ | |
| (error)->domain, (error)->code, text, NULL, __FILE__, GST_FUNCTION, \ | |
| __LINE__); \ | |
| } G_STMT_END | |
| #if SRT_VERSION_VALUE > 0x10402 | |
| #define REASON_FORMAT "s (%d)" | |
| #define REASON_ARGS(reason) srt_rejectreason_str (reason), (reason) | |
| #else | |
| /* srt_rejectreason_str() is unavailable in libsrt 1.4.2 and prior due to | |
| * unexported symbol. See https://github.com/Haivision/srt/pull/1728. */ | |
| #define REASON_FORMAT "s %d" | |
| #define REASON_ARGS(reason) "reject reason code", (reason) | |
| #endif | |
| /* Define options added in later revisions */ | |
| #if SRT_VERSION_VALUE < 0x10402 | |
| #define SRTO_DRIFTTRACER 37 | |
| /* We can't define SRTO_BINDTODEVICE since it depends on configuration flags *sigh* */ | |
| #define SRTO_RETRANSMITALGO 61 | |
| #endif | |
| enum | |
| { | |
| PROP_URI = 1, | |
| PROP_MODE, | |
| PROP_LOCALADDRESS, | |
| PROP_LOCALPORT, | |
| PROP_PASSPHRASE, | |
| PROP_PBKEYLEN, | |
| PROP_POLL_TIMEOUT, | |
| PROP_LATENCY, | |
| PROP_MSG_SIZE, | |
| PROP_STATS, | |
| PROP_WAIT_FOR_CONNECTION, | |
| PROP_STREAMID, | |
| PROP_AUTHENTICATION, | |
| PROP_AUTO_RECONNECT, | |
| PROP_LAST | |
| }; | |
| typedef struct | |
| { | |
| SRTSOCKET sock; | |
| gint poll_id; | |
| GSocketAddress *sockaddr; | |
| gboolean sent_headers; | |
| } SRTCaller; | |
| static SRTCaller * | |
| srt_caller_new (void) | |
| { | |
| SRTCaller *caller = g_new0 (SRTCaller, 1); | |
| caller->sock = SRT_INVALID_SOCK; | |
| caller->poll_id = SRT_ERROR; | |
| caller->sent_headers = FALSE; | |
| return caller; | |
| } | |
| static void | |
| srt_caller_free (SRTCaller * caller) | |
| { | |
| g_return_if_fail (caller != NULL); | |
| g_clear_object (&caller->sockaddr); | |
| if (caller->sock != SRT_INVALID_SOCK) { | |
| srt_close (caller->sock); | |
| } | |
| if (caller->poll_id != SRT_ERROR) { | |
| srt_epoll_release (caller->poll_id); | |
| } | |
| g_free (caller); | |
| } | |
| /* called with sock_lock */ | |
| static void | |
| srt_caller_signal_removed (SRTCaller * caller, GstSRTObject * srtobject) | |
| { | |
| g_signal_emit_by_name (srtobject->element, "caller-removed", 0, | |
| caller->sockaddr); | |
| } | |
| struct srt_constant_params | |
| { | |
| const gchar *name; | |
| SRT_SOCKOPT param; | |
| const void *val; | |
| int val_len; | |
| }; | |
| static const bool bool_false = false; | |
| static const bool bool_true = true; | |
| static const int32_t int32_zero = 0; | |
| static const struct linger no_linger = { 0, 0 }; | |
| /* *INDENT-OFF* */ | |
| static const struct srt_constant_params srt_params[] = { | |
| {"SRTO_SNDSYN", SRTO_SNDSYN, &bool_false, sizeof bool_false}, /* non-blocking */ | |
| {"SRTO_RCVSYN", SRTO_RCVSYN, &bool_false, sizeof bool_false}, /* non-blocking */ | |
| {"SRTO_LINGER", SRTO_LINGER, &no_linger, sizeof no_linger}, /* no linger time */ | |
| {"SRTO_TSBPDMODE", SRTO_TSBPDMODE, &bool_true, sizeof bool_true}, /* Timestamp-based Packet Delivery mode must be enabled */ | |
| {"SRTO_IPV6ONLY", SRTO_IPV6ONLY, &int32_zero, sizeof int32_zero}, /* must be either 0 or 1 before binding to :: */ | |
| {NULL, -1, NULL, 0}, | |
| }; | |
| /* *INDENT-ON* */ | |
| typedef struct | |
| { | |
| const gchar *name; | |
| SRT_SOCKOPT opt; | |
| GType gtype; | |
| } SrtOption; | |
| SrtOption srt_options[] = { | |
| {"mss", SRTO_MSS, G_TYPE_INT}, | |
| {"fc", SRTO_FC, G_TYPE_INT}, | |
| {"sndbuf", SRTO_SNDBUF, G_TYPE_INT}, | |
| {"rcvbuf", SRTO_RCVBUF, G_TYPE_INT}, | |
| {"maxbw", SRTO_MAXBW, G_TYPE_INT64}, | |
| {"tsbpdmode", SRTO_TSBPDMODE, G_TYPE_BOOLEAN}, | |
| {"latency", SRTO_LATENCY, G_TYPE_INT}, | |
| {"inputbw", SRTO_INPUTBW, G_TYPE_INT64}, | |
| {"oheadbw", SRTO_OHEADBW, G_TYPE_INT}, | |
| {"passphrase", SRTO_PASSPHRASE, G_TYPE_STRING}, | |
| {"pbkeylen", SRTO_PBKEYLEN, G_TYPE_INT}, | |
| {"ipttl", SRTO_IPTTL, G_TYPE_INT}, | |
| {"iptos", SRTO_IPTOS, G_TYPE_INT}, | |
| {"tlpktdrop", SRTO_TLPKTDROP, G_TYPE_BOOLEAN}, | |
| {"snddropdelay", SRTO_SNDDROPDELAY, G_TYPE_INT}, | |
| {"nakreport", SRTO_NAKREPORT, G_TYPE_BOOLEAN}, | |
| {"conntimeo", SRTO_CONNTIMEO, G_TYPE_INT}, | |
| {"drifttracer", SRTO_DRIFTTRACER, G_TYPE_BOOLEAN}, | |
| {"lossmaxttl", SRTO_LOSSMAXTTL, G_TYPE_INT}, | |
| {"rcvlatency", SRTO_RCVLATENCY, G_TYPE_INT}, | |
| {"peerlatency", SRTO_PEERLATENCY, G_TYPE_INT}, | |
| {"minversion", SRTO_MINVERSION, G_TYPE_INT}, | |
| {"streamid", SRTO_STREAMID, G_TYPE_STRING}, | |
| {"congestion", SRTO_CONGESTION, G_TYPE_STRING}, | |
| {"messageapi", SRTO_MESSAGEAPI, G_TYPE_BOOLEAN}, | |
| {"payloadsize", SRTO_PAYLOADSIZE, G_TYPE_INT}, | |
| {"transtype", SRTO_TRANSTYPE, G_TYPE_INT}, | |
| {"kmrefreshrate", SRTO_KMREFRESHRATE, G_TYPE_INT}, | |
| {"kmpreannounce", SRTO_KMPREANNOUNCE, G_TYPE_INT}, | |
| {"enforcedencryption", SRTO_ENFORCEDENCRYPTION, G_TYPE_BOOLEAN}, | |
| {"ipv6only", SRTO_IPV6ONLY, G_TYPE_INT}, | |
| {"peeridletimeo", SRTO_PEERIDLETIMEO, G_TYPE_INT}, | |
| #if SRT_VERSION_VALUE >= 0x10402 | |
| {"bindtodevice", SRTO_BINDTODEVICE, G_TYPE_STRING}, | |
| #endif | |
| {"packetfilter", SRTO_PACKETFILTER, G_TYPE_STRING}, | |
| {"retransmitalgo", SRTO_RETRANSMITALGO, G_TYPE_INT}, | |
| {NULL} | |
| }; | |
| static gint srt_init_refcount = 0; | |
| static GSocketAddress * | |
| gst_srt_object_resolve (GstSRTObject * srtobject, const gchar * address, | |
| guint port, GError ** err_out) | |
| { | |
| GError *err = NULL; | |
| GSocketAddress *saddr; | |
| GResolver *resolver; | |
| saddr = g_inet_socket_address_new_from_string (address, port); | |
| if (!saddr) { | |
| GList *results; | |
| GST_DEBUG_OBJECT (srtobject->element, "resolving IP address for host %s", | |
| address); | |
| resolver = g_resolver_get_default (); | |
| results = | |
| g_resolver_lookup_by_name (resolver, address, srtobject->cancellable, | |
| &err); | |
| if (!results) | |
| goto name_resolve; | |
| saddr = g_inet_socket_address_new (G_INET_ADDRESS (results->data), port); | |
| g_resolver_free_addresses (results); | |
| g_object_unref (resolver); | |
| } | |
| #ifndef GST_DISABLE_GST_DEBUG | |
| { | |
| gchar *ip = | |
| g_inet_address_to_string (g_inet_socket_address_get_address | |
| (G_INET_SOCKET_ADDRESS (saddr))); | |
| GST_DEBUG_OBJECT (srtobject->element, "IP address for host %s is %s", | |
| address, ip); | |
| g_free (ip); | |
| } | |
| #endif | |
| return saddr; | |
| name_resolve: | |
| { | |
| GST_WARNING_OBJECT (srtobject->element, "Failed to resolve %s: %s", address, | |
| err->message); | |
| g_set_error (err_out, GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_OPEN_READ, | |
| "Failed to resolve host '%s': %s", address, err->message); | |
| g_clear_error (&err); | |
| g_object_unref (resolver); | |
| return NULL; | |
| } | |
| } | |
| static gboolean | |
| gst_srt_object_apply_socket_option (SRTSOCKET sock, SrtOption * option, | |
| const GValue * value, GError ** error) | |
| { | |
| union | |
| { | |
| int32_t i; | |
| int64_t i64; | |
| gboolean b; | |
| const gchar *c; | |
| } u; | |
| const void *optval = &u; | |
| gint optlen; | |
| if (!G_VALUE_HOLDS (value, option->gtype)) { | |
| goto bad_type; | |
| } | |
| switch (option->gtype) { | |
| case G_TYPE_INT: | |
| u.i = g_value_get_int (value); | |
| optlen = sizeof u.i; | |
| break; | |
| case G_TYPE_INT64: | |
| u.i64 = g_value_get_int64 (value); | |
| optlen = sizeof u.i64; | |
| break; | |
| case G_TYPE_BOOLEAN: | |
| u.b = g_value_get_boolean (value); | |
| optlen = sizeof u.b; | |
| break; | |
| case G_TYPE_STRING: | |
| u.c = g_value_get_string (value); | |
| optval = u.c; | |
| optlen = u.c ? strlen (u.c) : 0; | |
| if (optlen == 0) { | |
| return TRUE; | |
| } | |
| break; | |
| default: | |
| goto bad_type; | |
| } | |
| if (srt_setsockopt (sock, 0, option->opt, optval, optlen)) { | |
| g_set_error (error, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_SETTINGS, | |
| "failed to set %s (reason: %s)", option->name, srt_getlasterror_str ()); | |
| return FALSE; | |
| } | |
| return TRUE; | |
| bad_type: | |
| g_set_error (error, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_SETTINGS, | |
| "option %s has unsupported type", option->name); | |
| return FALSE; | |
| } | |
| static gboolean | |
| gst_srt_object_set_common_params (SRTSOCKET sock, GstSRTObject * srtobject, | |
| GError ** error) | |
| { | |
| const struct srt_constant_params *params = srt_params; | |
| SrtOption *option = srt_options; | |
| GST_OBJECT_LOCK (srtobject->element); | |
| for (; params->name != NULL; params++) { | |
| if (srt_setsockopt (sock, 0, params->param, params->val, params->val_len)) { | |
| g_set_error (error, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_SETTINGS, | |
| "failed to set %s (reason: %s)", params->name, | |
| srt_getlasterror_str ()); | |
| goto err; | |
| } | |
| } | |
| for (; option->name; ++option) { | |
| const GValue *val; | |
| val = gst_structure_get_value (srtobject->parameters, option->name); | |
| if (val && !gst_srt_object_apply_socket_option (sock, option, val, error)) { | |
| goto err; | |
| } | |
| } | |
| GST_OBJECT_UNLOCK (srtobject->element); | |
| return TRUE; | |
| err: | |
| GST_OBJECT_UNLOCK (srtobject->element); | |
| return FALSE; | |
| } | |
| GstSRTObject * | |
| gst_srt_object_new (GstElement * element) | |
| { | |
| GstSRTObject *srtobject; | |
| gint fd, fd_flags = SRT_EPOLL_ERR | SRT_EPOLL_IN; | |
| if (g_atomic_int_add (&srt_init_refcount, 1) == 0) { | |
| GST_DEBUG_OBJECT (element, "Starting up SRT"); | |
| if (srt_startup () < 0) { | |
| g_warning ("Failed to initialize SRT (reason: %s)", | |
| srt_getlasterror_str ()); | |
| } | |
| } | |
| srtobject = g_new0 (GstSRTObject, 1); | |
| srtobject->element = element; | |
| srtobject->cancellable = g_cancellable_new (); | |
| srtobject->parameters = gst_structure_new_empty ("application/x-srt-params"); | |
| srtobject->sock = SRT_INVALID_SOCK; | |
| srtobject->poll_id = srt_epoll_create (); | |
| srtobject->sent_headers = FALSE; | |
| srtobject->wait_for_connection = GST_SRT_DEFAULT_WAIT_FOR_CONNECTION; | |
| srtobject->auto_reconnect = GST_SRT_DEFAULT_AUTO_RECONNECT; | |
| fd = g_cancellable_get_fd (srtobject->cancellable); | |
| if (fd >= 0) | |
| srt_epoll_add_ssock (srtobject->poll_id, fd, &fd_flags); | |
| g_cancellable_cancel (srtobject->cancellable); | |
| g_cond_init (&srtobject->sock_cond); | |
| return srtobject; | |
| } | |
| void | |
| gst_srt_object_destroy (GstSRTObject * srtobject) | |
| { | |
| g_return_if_fail (srtobject != NULL); | |
| if (srtobject->sock != SRT_INVALID_SOCK) { | |
| srt_close (srtobject->sock); | |
| } | |
| srt_epoll_release (srtobject->poll_id); | |
| g_cond_clear (&srtobject->sock_cond); | |
| GST_DEBUG_OBJECT (srtobject->element, "Destroying srtobject"); | |
| gst_structure_free (srtobject->parameters); | |
| if (g_atomic_int_dec_and_test (&srt_init_refcount)) { | |
| srt_cleanup (); | |
| GST_DEBUG_OBJECT (srtobject->element, "Cleaning up SRT"); | |
| } | |
| g_clear_pointer (&srtobject->uri, gst_uri_unref); | |
| g_clear_object (&srtobject->cancellable); | |
| g_free (srtobject); | |
| } | |
| gboolean | |
| gst_srt_object_set_property_helper (GstSRTObject * srtobject, | |
| guint prop_id, const GValue * value, GParamSpec * pspec) | |
| { | |
| GST_OBJECT_LOCK (srtobject->element); | |
| switch (prop_id) { | |
| case PROP_URI: | |
| gst_srt_object_set_uri (srtobject, g_value_get_string (value), NULL); | |
| break; | |
| case PROP_MODE: | |
| gst_structure_set_value (srtobject->parameters, "mode", value); | |
| break; | |
| case PROP_POLL_TIMEOUT: | |
| gst_structure_set_value (srtobject->parameters, "poll-timeout", value); | |
| break; | |
| case PROP_LATENCY: | |
| gst_structure_set_value (srtobject->parameters, "latency", value); | |
| break; | |
| case PROP_LOCALADDRESS: | |
| gst_structure_set_value (srtobject->parameters, "localaddress", value); | |
| break; | |
| case PROP_LOCALPORT: | |
| gst_structure_set_value (srtobject->parameters, "localport", value); | |
| break; | |
| case PROP_PASSPHRASE: | |
| gst_structure_set_value (srtobject->parameters, "passphrase", value); | |
| break; | |
| case PROP_PBKEYLEN: | |
| gst_structure_set (srtobject->parameters, "pbkeylen", G_TYPE_INT, | |
| g_value_get_enum (value), NULL); | |
| break; | |
| case PROP_WAIT_FOR_CONNECTION: | |
| srtobject->wait_for_connection = g_value_get_boolean (value); | |
| break; | |
| case PROP_STREAMID: | |
| gst_structure_set_value (srtobject->parameters, "streamid", value); | |
| break; | |
| case PROP_AUTHENTICATION: | |
| srtobject->authentication = g_value_get_boolean (value); | |
| break; | |
| case PROP_AUTO_RECONNECT: | |
| srtobject->auto_reconnect = g_value_get_boolean (value); | |
| break; | |
| default: | |
| goto err; | |
| } | |
| GST_OBJECT_UNLOCK (srtobject->element); | |
| return TRUE; | |
| err: | |
| GST_OBJECT_UNLOCK (srtobject->element); | |
| return FALSE; | |
| } | |
| gboolean | |
| gst_srt_object_get_property_helper (GstSRTObject * srtobject, | |
| guint prop_id, GValue * value, GParamSpec * pspec) | |
| { | |
| switch (prop_id) { | |
| case PROP_URI: | |
| GST_OBJECT_LOCK (srtobject->element); | |
| g_value_take_string (value, gst_uri_to_string (srtobject->uri)); | |
| GST_OBJECT_UNLOCK (srtobject->element); | |
| break; | |
| case PROP_MODE:{ | |
| GstSRTConnectionMode v; | |
| GST_OBJECT_LOCK (srtobject->element); | |
| if (!gst_structure_get_enum (srtobject->parameters, "mode", | |
| GST_TYPE_SRT_CONNECTION_MODE, (gint *) & v)) { | |
| GST_WARNING_OBJECT (srtobject->element, "Failed to get 'mode'"); | |
| v = GST_SRT_CONNECTION_MODE_NONE; | |
| } | |
| g_value_set_enum (value, v); | |
| GST_OBJECT_UNLOCK (srtobject->element); | |
| break; | |
| } | |
| case PROP_LOCALADDRESS: | |
| GST_OBJECT_LOCK (srtobject->element); | |
| g_value_set_string (value, | |
| gst_structure_get_string (srtobject->parameters, "localaddress")); | |
| GST_OBJECT_UNLOCK (srtobject->element); | |
| break; | |
| case PROP_LOCALPORT:{ | |
| guint v; | |
| GST_OBJECT_LOCK (srtobject->element); | |
| if (!gst_structure_get_uint (srtobject->parameters, "localport", &v)) { | |
| GST_WARNING_OBJECT (srtobject->element, "Failed to get 'localport'"); | |
| v = GST_SRT_DEFAULT_PORT; | |
| } | |
| g_value_set_uint (value, v); | |
| GST_OBJECT_UNLOCK (srtobject->element); | |
| break; | |
| } | |
| case PROP_PBKEYLEN:{ | |
| GstSRTKeyLength v; | |
| GST_OBJECT_LOCK (srtobject->element); | |
| if (!gst_structure_get_int (srtobject->parameters, "pbkeylen", | |
| (gint *) & v)) { | |
| GST_WARNING_OBJECT (srtobject->element, "Failed to get 'pbkeylen'"); | |
| v = GST_SRT_KEY_LENGTH_NO_KEY; | |
| } | |
| g_value_set_enum (value, v); | |
| GST_OBJECT_UNLOCK (srtobject->element); | |
| break; | |
| } | |
| case PROP_POLL_TIMEOUT:{ | |
| gint v; | |
| GST_OBJECT_LOCK (srtobject->element); | |
| if (!gst_structure_get_int (srtobject->parameters, "poll-timeout", &v)) { | |
| GST_WARNING_OBJECT (srtobject->element, "Failed to get 'poll-timeout'"); | |
| v = GST_SRT_DEFAULT_POLL_TIMEOUT; | |
| } | |
| g_value_set_int (value, v); | |
| GST_OBJECT_UNLOCK (srtobject->element); | |
| break; | |
| } | |
| case PROP_LATENCY:{ | |
| gint v; | |
| GST_OBJECT_LOCK (srtobject->element); | |
| if (!gst_structure_get_int (srtobject->parameters, "latency", &v)) { | |
| GST_WARNING_OBJECT (srtobject->element, "Failed to get 'latency'"); | |
| v = GST_SRT_DEFAULT_LATENCY; | |
| } | |
| g_value_set_int (value, v); | |
| GST_OBJECT_UNLOCK (srtobject->element); | |
| break; | |
| } | |
| case PROP_STATS: | |
| g_value_take_boxed (value, gst_srt_object_get_stats (srtobject)); | |
| break; | |
| case PROP_WAIT_FOR_CONNECTION: | |
| GST_OBJECT_LOCK (srtobject->element); | |
| g_value_set_boolean (value, srtobject->wait_for_connection); | |
| GST_OBJECT_UNLOCK (srtobject->element); | |
| break; | |
| case PROP_STREAMID: | |
| GST_OBJECT_LOCK (srtobject->element); | |
| g_value_set_string (value, | |
| gst_structure_get_string (srtobject->parameters, "streamid")); | |
| GST_OBJECT_UNLOCK (srtobject->element); | |
| break; | |
| case PROP_AUTHENTICATION: | |
| GST_OBJECT_LOCK (srtobject->element); | |
| g_value_set_boolean (value, srtobject->authentication); | |
| GST_OBJECT_UNLOCK (srtobject->element); | |
| break; | |
| case PROP_AUTO_RECONNECT: | |
| GST_OBJECT_LOCK (srtobject->element); | |
| g_value_set_boolean (value, srtobject->auto_reconnect); | |
| GST_OBJECT_UNLOCK (srtobject->element); | |
| break; | |
| default: | |
| return FALSE; | |
| } | |
| return TRUE; | |
| } | |
| void | |
| gst_srt_object_install_properties_helper (GObjectClass * gobject_class) | |
| { | |
| /** | |
| * GstSRTSrc:uri: | |
| * | |
| * The URI used by SRT connection. User can specify SRT specific options by URI parameters. | |
| * Refer to <a href="https://github.com/Haivision/srt/blob/master/docs/stransmit.md#medium-srt">Mediun: SRT</a> | |
| */ | |
| g_object_class_install_property (gobject_class, PROP_URI, | |
| g_param_spec_string ("uri", "URI", | |
| "URI in the form of srt://address:port", GST_SRT_DEFAULT_URI, | |
| G_PARAM_READWRITE | GST_PARAM_MUTABLE_READY | | |
| G_PARAM_STATIC_STRINGS)); | |
| /** | |
| * GstSRTSrc:mode: | |
| * | |
| * The SRT connection mode. | |
| * This property can be set by URI parameters. | |
| */ | |
| g_object_class_install_property (gobject_class, PROP_MODE, | |
| g_param_spec_enum ("mode", "Connection mode", | |
| "SRT connection mode", GST_TYPE_SRT_CONNECTION_MODE, | |
| GST_SRT_CONNECTION_MODE_CALLER, | |
| G_PARAM_READWRITE | GST_PARAM_MUTABLE_READY | | |
| G_PARAM_STATIC_STRINGS)); | |
| gst_type_mark_as_plugin_api (GST_TYPE_SRT_CONNECTION_MODE, 0); | |
| /** | |
| * GstSRTSrc:localaddress: | |
| * | |
| * The address to bind when #GstSRTSrc:mode is listener or rendezvous. | |
| * This property can be set by URI parameters. | |
| */ | |
| g_object_class_install_property (gobject_class, PROP_LOCALADDRESS, | |
| g_param_spec_string ("localaddress", "Local address", | |
| "Local address to bind", GST_SRT_DEFAULT_LOCALADDRESS, | |
| G_PARAM_READWRITE | GST_PARAM_MUTABLE_READY | | |
| G_PARAM_STATIC_STRINGS)); | |
| /** | |
| * GstSRTSrc:localport: | |
| * | |
| * The local port to bind when #GstSRTSrc:mode is listener or rendezvous. | |
| * This property can be set by URI parameters. | |
| */ | |
| g_object_class_install_property (gobject_class, PROP_LOCALPORT, | |
| g_param_spec_uint ("localport", "Local port", | |
| "Local port to bind", 0, | |
| 65535, GST_SRT_DEFAULT_PORT, | |
| G_PARAM_READWRITE | GST_PARAM_MUTABLE_READY | | |
| G_PARAM_STATIC_STRINGS)); | |
| /** | |
| * GstSRTSrc:passphrase: | |
| * | |
| * The password for the encrypted transmission. | |
| * This property can be set by URI parameters. | |
| */ | |
| g_object_class_install_property (gobject_class, PROP_PASSPHRASE, | |
| g_param_spec_string ("passphrase", "Passphrase", | |
| "Password for the encrypted transmission", "", | |
| G_PARAM_WRITABLE | GST_PARAM_MUTABLE_READY | G_PARAM_STATIC_STRINGS)); | |
| /** | |
| * GstSRTSrc:pbkeylen: | |
| * | |
| * The crypto key length. | |
| * This property can be set by URI parameters. | |
| */ | |
| g_object_class_install_property (gobject_class, PROP_PBKEYLEN, | |
| g_param_spec_enum ("pbkeylen", "Crypto key length", | |
| "Crypto key length in bytes", GST_TYPE_SRT_KEY_LENGTH, | |
| GST_SRT_DEFAULT_PBKEYLEN, | |
| G_PARAM_READWRITE | GST_PARAM_MUTABLE_READY | | |
| G_PARAM_STATIC_STRINGS)); | |
| gst_type_mark_as_plugin_api (GST_TYPE_SRT_KEY_LENGTH, 0); | |
| /** | |
| * GstSRTSrc:poll-timeout: | |
| * | |
| * The polling timeout used when srt poll is started. | |
| * Even if the default value indicates infinite waiting, it can be cancellable according to #GstState | |
| * This property can be set by URI parameters. | |
| */ | |
| g_object_class_install_property (gobject_class, PROP_POLL_TIMEOUT, | |
| g_param_spec_int ("poll-timeout", "Poll timeout", | |
| "Return poll wait after timeout milliseconds (-1 = infinite)", -1, | |
| G_MAXINT32, GST_SRT_DEFAULT_POLL_TIMEOUT, | |
| G_PARAM_READWRITE | GST_PARAM_MUTABLE_READY | | |
| G_PARAM_STATIC_STRINGS)); | |
| /** | |
| * GstSRTSrc:latency: | |
| * | |
| * The maximum accepted transmission latency. | |
| */ | |
| g_object_class_install_property (gobject_class, PROP_LATENCY, | |
| g_param_spec_int ("latency", "latency", | |
| "Minimum latency (milliseconds)", 0, | |
| G_MAXINT32, GST_SRT_DEFAULT_LATENCY, | |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); | |
| /** | |
| * GstSRTSrc:stats: | |
| * | |
| * The statistics from SRT. | |
| */ | |
| g_object_class_install_property (gobject_class, PROP_STATS, | |
| g_param_spec_boxed ("stats", "Statistics", | |
| "SRT Statistics", GST_TYPE_STRUCTURE, | |
| G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); | |
| /** | |
| * GstSRTSink:wait-for-connection: | |
| * | |
| * Boolean to block streaming until a client connects. If TRUE, | |
| * `srtsink' will stream only when a client is connected. | |
| */ | |
| g_object_class_install_property (gobject_class, PROP_WAIT_FOR_CONNECTION, | |
| g_param_spec_boolean ("wait-for-connection", | |
| "Wait for a connection", | |
| "Block the stream until a client connects", | |
| GST_SRT_DEFAULT_WAIT_FOR_CONNECTION, | |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); | |
| /** | |
| * GstSRTSrc:streamid: | |
| * | |
| * The stream id for the SRT access control. | |
| */ | |
| g_object_class_install_property (gobject_class, PROP_STREAMID, | |
| g_param_spec_string ("streamid", "Stream ID", | |
| "Stream ID for the SRT access control", "", | |
| G_PARAM_READWRITE | GST_PARAM_MUTABLE_READY | | |
| G_PARAM_STATIC_STRINGS)); | |
| /** | |
| * GstSRTSink:authentication: | |
| * | |
| * Boolean to authenticate a connection. If TRUE, | |
| * the incoming connection is authenticated. Else, | |
| * all the connections are accepted. | |
| * | |
| * Since: 1.20 | |
| * | |
| */ | |
| g_object_class_install_property (gobject_class, PROP_AUTHENTICATION, | |
| g_param_spec_boolean ("authentication", | |
| "Authentication", | |
| "Authenticate a connection", | |
| FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); | |
| /** | |
| * GstSRTSrc:auto-reconnect: | |
| * | |
| * Boolean to choose whether to automatically reconnect. If TRUE, an element | |
| * in caller mode will try to reconnect instead of reporting an error. | |
| * | |
| * This will be ignored for authentication failures. | |
| * | |
| * Since: 1.22 | |
| * | |
| */ | |
| g_object_class_install_property (gobject_class, PROP_AUTO_RECONNECT, | |
| g_param_spec_boolean ("auto-reconnect", | |
| "Automatic reconnect", | |
| "Automatically reconnect when connection fails", | |
| GST_SRT_DEFAULT_AUTO_RECONNECT, | |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); | |
| } | |
| static void | |
| gst_srt_object_set_enum_value (GstStructure * s, GType enum_type, | |
| gconstpointer key, gconstpointer value) | |
| { | |
| GEnumClass *enum_class; | |
| GEnumValue *enum_value; | |
| enum_class = g_type_class_ref (enum_type); | |
| enum_value = g_enum_get_value_by_nick (enum_class, value); | |
| if (enum_value) { | |
| gst_structure_set (s, key, enum_type, enum_value->value, NULL); | |
| } | |
| g_type_class_unref (enum_class); | |
| } | |
| static void | |
| gst_srt_object_set_string_value (GstStructure * s, const gchar * key, | |
| const gchar * value) | |
| { | |
| gst_structure_set (s, key, G_TYPE_STRING, value, NULL); | |
| } | |
| static void | |
| gst_srt_object_set_uint_value (GstStructure * s, const gchar * key, | |
| const gchar * value) | |
| { | |
| gst_structure_set (s, key, G_TYPE_UINT, | |
| (guint) g_ascii_strtoll (value, NULL, 10), NULL); | |
| } | |
| static void | |
| gst_srt_object_set_int_value (GstStructure * s, const gchar * key, | |
| const gchar * value) | |
| { | |
| gst_structure_set (s, key, G_TYPE_INT, | |
| (gint) g_ascii_strtoll (value, NULL, 10), NULL); | |
| } | |
| static void | |
| gst_srt_object_set_int64_value (GstStructure * s, const gchar * key, | |
| const gchar * value) | |
| { | |
| gst_structure_set (s, key, G_TYPE_INT64, | |
| g_ascii_strtoll (value, NULL, 10), NULL); | |
| } | |
| static void | |
| gst_srt_object_set_boolean_value (GstStructure * s, const gchar * key, | |
| const gchar * value) | |
| { | |
| gboolean bool_val; | |
| static const gchar *true_names[] = { | |
| "1", "yes", "on", "true", NULL | |
| }; | |
| static const gchar *false_names[] = { | |
| "0", "no", "off", "false", NULL | |
| }; | |
| if (g_strv_contains (true_names, value)) { | |
| bool_val = TRUE; | |
| } else if (g_strv_contains (false_names, value)) { | |
| bool_val = FALSE; | |
| } else { | |
| return; | |
| } | |
| gst_structure_set (s, key, G_TYPE_BOOLEAN, bool_val, NULL); | |
| } | |
| static void | |
| gst_srt_object_set_socket_option (GstStructure * s, const gchar * key, | |
| const gchar * value) | |
| { | |
| SrtOption *option = srt_options; | |
| for (; option->name; ++option) { | |
| if (g_str_equal (key, option->name)) { | |
| switch (option->gtype) { | |
| case G_TYPE_INT: | |
| gst_srt_object_set_int_value (s, key, value); | |
| break; | |
| case G_TYPE_INT64: | |
| gst_srt_object_set_int64_value (s, key, value); | |
| break; | |
| case G_TYPE_STRING: | |
| gst_srt_object_set_string_value (s, key, value); | |
| break; | |
| case G_TYPE_BOOLEAN: | |
| gst_srt_object_set_boolean_value (s, key, value); | |
| break; | |
| } | |
| break; | |
| } | |
| } | |
| } | |
| static void | |
| gst_srt_object_validate_parameters (GstStructure * s, GstUri * uri) | |
| { | |
| GstSRTConnectionMode connection_mode = GST_SRT_CONNECTION_MODE_NONE; | |
| gst_structure_get_enum (s, "mode", GST_TYPE_SRT_CONNECTION_MODE, | |
| (gint *) & connection_mode); | |
| if (connection_mode == GST_SRT_CONNECTION_MODE_RENDEZVOUS || | |
| connection_mode == GST_SRT_CONNECTION_MODE_LISTENER) { | |
| guint local_port; | |
| const gchar *local_address = gst_structure_get_string (s, "localaddress"); | |
| if (local_address == NULL) { | |
| local_address = | |
| gst_uri_get_host (uri) == | |
| NULL ? GST_SRT_DEFAULT_LOCALADDRESS : gst_uri_get_host (uri); | |
| gst_srt_object_set_string_value (s, "localaddress", local_address); | |
| } | |
| if (!gst_structure_get_uint (s, "localport", &local_port)) { | |
| local_port = | |
| gst_uri_get_port (uri) == | |
| GST_URI_NO_PORT ? GST_SRT_DEFAULT_PORT : gst_uri_get_port (uri); | |
| gst_structure_set (s, "localport", G_TYPE_UINT, local_port, NULL); | |
| } | |
| } | |
| } | |
| /* called with GST_OBJECT_LOCK (srtobject->element) held */ | |
| gboolean | |
| gst_srt_object_set_uri (GstSRTObject * srtobject, const gchar * uri, | |
| GError ** err) | |
| { | |
| GHashTable *query_table = NULL; | |
| GHashTableIter iter; | |
| gpointer key, value; | |
| const char *addr_str; | |
| if (srtobject->opened) { | |
| g_warning | |
| ("It's not supported to change the 'uri' property when SRT socket is opened."); | |
| g_set_error (err, GST_URI_ERROR, GST_URI_ERROR_BAD_STATE, | |
| "It's not supported to change the 'uri' property when SRT socket is opened"); | |
| return FALSE; | |
| } | |
| if (!g_str_has_prefix (uri, GST_SRT_DEFAULT_URI_SCHEME)) { | |
| g_warning ("Given uri cannot be used for SRT connection."); | |
| g_set_error (err, GST_URI_ERROR, GST_URI_ERROR_BAD_URI, | |
| "Invalid SRT URI scheme"); | |
| return FALSE; | |
| } | |
| g_clear_pointer (&srtobject->uri, gst_uri_unref); | |
| srtobject->uri = gst_uri_from_string (uri); | |
| g_clear_pointer (&srtobject->parameters, gst_structure_free); | |
| srtobject->parameters = gst_structure_new ("application/x-srt-params", | |
| "poll-timeout", G_TYPE_INT, GST_SRT_DEFAULT_POLL_TIMEOUT, | |
| "latency", G_TYPE_INT, GST_SRT_DEFAULT_LATENCY, NULL); | |
| query_table = gst_uri_get_query_table (srtobject->uri); | |
| GST_DEBUG_OBJECT (srtobject->element, | |
| "set uri to (host: %s, port: %d) with %d query strings", | |
| gst_uri_get_host (srtobject->uri), gst_uri_get_port (srtobject->uri), | |
| query_table == NULL ? 0 : g_hash_table_size (query_table)); | |
| addr_str = gst_uri_get_host (srtobject->uri); | |
| if (addr_str) | |
| gst_srt_object_set_enum_value (srtobject->parameters, | |
| GST_TYPE_SRT_CONNECTION_MODE, "mode", "caller"); | |
| else | |
| gst_srt_object_set_enum_value (srtobject->parameters, | |
| GST_TYPE_SRT_CONNECTION_MODE, "mode", "listener"); | |
| if (query_table) { | |
| g_hash_table_iter_init (&iter, query_table); | |
| while (g_hash_table_iter_next (&iter, &key, &value)) { | |
| if (!g_strcmp0 ("mode", key)) { | |
| gst_srt_object_set_enum_value (srtobject->parameters, | |
| GST_TYPE_SRT_CONNECTION_MODE, key, value); | |
| } else if (!g_strcmp0 ("localaddress", key)) { | |
| gst_srt_object_set_string_value (srtobject->parameters, key, value); | |
| } else if (!g_strcmp0 ("localport", key)) { | |
| gst_srt_object_set_uint_value (srtobject->parameters, key, value); | |
| } else if (!g_strcmp0 ("poll-timeout", key)) { | |
| gst_srt_object_set_int_value (srtobject->parameters, key, value); | |
| } else { | |
| gst_srt_object_set_socket_option (srtobject->parameters, key, value); | |
| } | |
| } | |
| g_hash_table_unref (query_table); | |
| } | |
| gst_srt_object_validate_parameters (srtobject->parameters, srtobject->uri); | |
| return TRUE; | |
| } | |
| static gpointer | |
| thread_func (gpointer data) | |
| { | |
| GstSRTObject *srtobject = data; | |
| gint poll_timeout; | |
| GST_OBJECT_LOCK (srtobject->element); | |
| if (!gst_structure_get_int (srtobject->parameters, "poll-timeout", | |
| &poll_timeout)) { | |
| poll_timeout = GST_SRT_DEFAULT_POLL_TIMEOUT; | |
| } | |
| GST_OBJECT_UNLOCK (srtobject->element); | |
| for (;;) { | |
| SRTSOCKET rsock; | |
| gint rsocklen = 1; | |
| SYSSOCKET rsys, wsys; | |
| gint rsyslen = 1, wsyslen = 1; | |
| gint ret; | |
| SRTSOCKET caller_sock; | |
| union | |
| { | |
| struct sockaddr_storage ss; | |
| struct sockaddr sa; | |
| } caller_sa; | |
| int caller_sa_len = sizeof (caller_sa); | |
| SRTCaller *caller; | |
| gint flag = SRT_EPOLL_ERR; | |
| gint fd, fd_flags = SRT_EPOLL_ERR | SRT_EPOLL_IN; | |
| GST_OBJECT_LOCK (srtobject->element); | |
| if (!srtobject->opened) { | |
| GST_OBJECT_UNLOCK (srtobject->element); | |
| break; | |
| } | |
| GST_OBJECT_UNLOCK (srtobject->element); | |
| switch (srt_getsockstate (srtobject->sock)) { | |
| case SRTS_BROKEN: | |
| case SRTS_CLOSING: | |
| case SRTS_CLOSED: | |
| case SRTS_NONEXIST: | |
| GST_ELEMENT_ERROR (srtobject->element, RESOURCE, FAILED, | |
| ("Socket is broken or closed"), (NULL)); | |
| return NULL; | |
| default: | |
| break; | |
| } | |
| GST_TRACE_OBJECT (srtobject->element, "Waiting on listening socket"); | |
| ret = | |
| srt_epoll_wait (srtobject->poll_id, &rsock, &rsocklen, NULL, 0, | |
| poll_timeout, &rsys, &rsyslen, &wsys, &wsyslen); | |
| GST_OBJECT_LOCK (srtobject->element); | |
| if (!srtobject->opened) { | |
| GST_OBJECT_UNLOCK (srtobject->element); | |
| break; | |
| } | |
| GST_OBJECT_UNLOCK (srtobject->element); | |
| if (ret < 0) { | |
| gint srt_errno = srt_getlasterror (NULL); | |
| if (srt_errno == SRT_ETIMEOUT) | |
| continue; | |
| GST_ELEMENT_ERROR (srtobject->element, RESOURCE, FAILED, | |
| ("Failed to poll socket: %s", srt_getlasterror_str ()), (NULL)); | |
| return NULL; | |
| } | |
| if (rsocklen != 1) | |
| continue; | |
| caller_sock = srt_accept (rsock, &caller_sa.sa, &caller_sa_len); | |
| if (caller_sock == SRT_INVALID_SOCK) { | |
| GST_ELEMENT_ERROR (srtobject->element, RESOURCE, FAILED, | |
| ("Failed to accept connection: %s", srt_getlasterror_str ()), (NULL)); | |
| return NULL; | |
| } | |
| caller = srt_caller_new (); | |
| caller->sockaddr = | |
| g_socket_address_new_from_native (&caller_sa.sa, caller_sa_len); | |
| caller->poll_id = srt_epoll_create (); | |
| caller->sock = caller_sock; | |
| fd = g_cancellable_get_fd (srtobject->cancellable); | |
| if (fd >= 0) | |
| srt_epoll_add_ssock (srtobject->poll_id, fd, &fd_flags); | |
| if (gst_uri_handler_get_uri_type (GST_URI_HANDLER | |
| (srtobject->element)) == GST_URI_SRC) { | |
| flag |= SRT_EPOLL_IN; | |
| } else { | |
| flag |= SRT_EPOLL_OUT; | |
| } | |
| if (srt_epoll_add_usock (caller->poll_id, caller_sock, &flag) < 0) { | |
| GST_ELEMENT_WARNING (srtobject->element, LIBRARY, SETTINGS, | |
| ("%s", srt_getlasterror_str ()), (NULL)); | |
| srt_caller_free (caller); | |
| /* try-again */ | |
| continue; | |
| } | |
| GST_DEBUG_OBJECT (srtobject->element, "Accept to connect %d", caller->sock); | |
| g_mutex_lock (&srtobject->sock_lock); | |
| srtobject->callers = g_list_prepend (srtobject->callers, caller); | |
| g_cond_signal (&srtobject->sock_cond); | |
| g_mutex_unlock (&srtobject->sock_lock); | |
| /* notifying caller-added */ | |
| g_signal_emit_by_name (srtobject->element, "caller-added", 0, | |
| caller->sockaddr); | |
| if (gst_uri_handler_get_uri_type (GST_URI_HANDLER (srtobject->element)) == | |
| GST_URI_SRC) | |
| break; | |
| } | |
| return NULL; | |
| } | |
| static GSocketAddress * | |
| peeraddr_to_g_socket_address (const struct sockaddr *peeraddr) | |
| { | |
| gsize peeraddr_len; | |
| switch (peeraddr->sa_family) { | |
| case AF_INET: | |
| peeraddr_len = sizeof (struct sockaddr_in); | |
| break; | |
| case AF_INET6: | |
| peeraddr_len = sizeof (struct sockaddr_in6); | |
| break; | |
| default: | |
| g_warning ("Unsupported address family %d", peeraddr->sa_family); | |
| return NULL; | |
| } | |
| return g_socket_address_new_from_native ((gpointer) peeraddr, peeraddr_len); | |
| } | |
| static gint | |
| srt_listen_callback_func (GstSRTObject * self, SRTSOCKET sock, int hs_version, | |
| const struct sockaddr *peeraddr, const char *stream_id) | |
| { | |
| GSocketAddress *addr = peeraddr_to_g_socket_address (peeraddr); | |
| if (!addr) { | |
| GST_WARNING_OBJECT (self->element, | |
| "Invalid peer address. Rejecting sink %d streamid: %s", sock, | |
| stream_id); | |
| return -1; | |
| } | |
| if (self->authentication) { | |
| gboolean authenticated = FALSE; | |
| /* notifying caller-connecting */ | |
| g_signal_emit_by_name (self->element, "caller-connecting", addr, | |
| stream_id, &authenticated); | |
| if (!authenticated) | |
| goto reject; | |
| } | |
| GST_DEBUG_OBJECT (self->element, | |
| "Accepting sink %d streamid: %s", sock, stream_id); | |
| g_object_unref (addr); | |
| return 0; | |
| reject: | |
| /* notifying caller-rejected */ | |
| GST_WARNING_OBJECT (self->element, | |
| "Rejecting sink %d streamid: %s", sock, stream_id); | |
| g_signal_emit_by_name (self->element, "caller-rejected", addr, stream_id); | |
| g_object_unref (addr); | |
| return -1; | |
| } | |
| static gboolean | |
| gst_srt_object_wait_connect (GstSRTObject * srtobject, gpointer sa, | |
| size_t sa_len, GError ** error) | |
| { | |
| SRTSOCKET sock = SRT_INVALID_SOCK; | |
| const gchar *local_address = NULL; | |
| guint local_port = 0; | |
| gint sock_flags = SRT_EPOLL_ERR | SRT_EPOLL_IN; | |
| gboolean poll_added = FALSE; | |
| gpointer bind_sa; | |
| gsize bind_sa_len; | |
| GSocketAddress *bind_addr = NULL; | |
| GST_OBJECT_LOCK (srtobject->element); | |
| gst_structure_get_uint (srtobject->parameters, "localport", &local_port); | |
| local_address = | |
| gst_structure_get_string (srtobject->parameters, "localaddress"); | |
| if (local_address == NULL) | |
| local_address = GST_SRT_DEFAULT_LOCALADDRESS; | |
| GST_OBJECT_UNLOCK (srtobject->element); | |
| bind_addr = | |
| gst_srt_object_resolve (srtobject, local_address, local_port, error); | |
| if (!bind_addr) { | |
| goto failed; | |
| } | |
| bind_sa_len = g_socket_address_get_native_size (bind_addr); | |
| bind_sa = g_alloca (bind_sa_len); | |
| if (!g_socket_address_to_native (bind_addr, bind_sa, bind_sa_len, error)) { | |
| goto failed; | |
| } | |
| g_clear_object (&bind_addr); | |
| sock = srt_create_socket (); | |
| if (sock == SRT_INVALID_SOCK) { | |
| g_set_error (error, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_INIT, "%s", | |
| srt_getlasterror_str ()); | |
| goto failed; | |
| } | |
| if (!gst_srt_object_set_common_params (sock, srtobject, error)) { | |
| goto failed; | |
| } | |
| GST_DEBUG_OBJECT (srtobject->element, "Binding to %s (port: %d)", | |
| local_address, local_port); | |
| if (srt_bind (sock, bind_sa, bind_sa_len) == SRT_ERROR) { | |
| g_set_error (error, GST_RESOURCE_ERROR, | |
| GST_RESOURCE_ERROR_OPEN_READ_WRITE, "Cannot bind to %s:%d - %s", | |
| local_address, local_port, srt_getlasterror_str ()); | |
| goto failed; | |
| } | |
| if (srt_epoll_add_usock (srtobject->poll_id, sock, &sock_flags) < 0) { | |
| g_set_error (error, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_SETTINGS, "%s", | |
| srt_getlasterror_str ()); | |
| goto failed; | |
| } | |
| poll_added = TRUE; | |
| srtobject->sock = sock; | |
| /* Register the SRT listen callback */ | |
| if (srt_listen_callback (srtobject->sock, | |
| (srt_listen_callback_fn *) srt_listen_callback_func, srtobject)) { | |
| g_set_error (error, GST_RESOURCE_ERROR, | |
| GST_RESOURCE_ERROR_OPEN_READ_WRITE, | |
| "Failed to register SRT listen callback: %s", srt_getlasterror_str ()); | |
| goto failed; | |
| } | |
| GST_DEBUG_OBJECT (srtobject->element, "Starting to listen on bind socket"); | |
| if (srt_listen (sock, 1) == SRT_ERROR) { | |
| g_set_error (error, GST_RESOURCE_ERROR, | |
| GST_RESOURCE_ERROR_OPEN_READ_WRITE, "Cannot listen on bind socket: %s", | |
| srt_getlasterror_str ()); | |
| goto failed; | |
| } | |
| srtobject->thread = | |
| g_thread_try_new ("GstSRTObjectListener", thread_func, srtobject, error); | |
| if (srtobject->thread == NULL) { | |
| GST_ERROR_OBJECT (srtobject->element, "Failed to start thread"); | |
| goto failed; | |
| } | |
| return TRUE; | |
| failed: | |
| if (poll_added) { | |
| srt_epoll_remove_usock (srtobject->poll_id, sock); | |
| } | |
| if (sock != SRT_INVALID_SOCK) { | |
| srt_close (sock); | |
| } | |
| g_clear_object (&bind_addr); | |
| srtobject->sock = SRT_INVALID_SOCK; | |
| return FALSE; | |
| } | |
| static gboolean | |
| gst_srt_object_connect (GstSRTObject * srtobject, | |
| GstSRTConnectionMode connection_mode, gpointer sa, size_t sa_len, | |
| GError ** error) | |
| { | |
| SRTSOCKET sock; | |
| gint sock_flags = SRT_EPOLL_ERR; | |
| guint local_port = 0; | |
| const gchar *local_address = NULL; | |
| bool sender; | |
| bool rendezvous; | |
| sock = srt_create_socket (); | |
| if (sock == SRT_INVALID_SOCK) { | |
| g_set_error (error, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_INIT, "%s", | |
| srt_getlasterror_str ()); | |
| return FALSE; | |
| } | |
| if (!gst_srt_object_set_common_params (sock, srtobject, error)) { | |
| goto failed; | |
| } | |
| switch (gst_uri_handler_get_uri_type (GST_URI_HANDLER (srtobject->element))) { | |
| case GST_URI_SRC: | |
| sender = false; | |
| sock_flags |= SRT_EPOLL_IN; | |
| break; | |
| case GST_URI_SINK: | |
| sender = true; | |
| sock_flags |= SRT_EPOLL_OUT; | |
| break; | |
| default: | |
| g_set_error (error, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_SETTINGS, | |
| "Cannot determine stream direction"); | |
| goto failed; | |
| } | |
| if (srt_setsockopt (sock, 0, SRTO_SENDER, &sender, sizeof sender)) { | |
| g_set_error (error, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_SETTINGS, "%s", | |
| srt_getlasterror_str ()); | |
| goto failed; | |
| } | |
| rendezvous = (connection_mode == GST_SRT_CONNECTION_MODE_RENDEZVOUS); | |
| if (srt_setsockopt (sock, 0, SRTO_RENDEZVOUS, &rendezvous, sizeof rendezvous)) { | |
| g_set_error (error, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_SETTINGS, "%s", | |
| srt_getlasterror_str ()); | |
| goto failed; | |
| } | |
| GST_OBJECT_LOCK (srtobject->element); | |
| gst_structure_get_uint (srtobject->parameters, "localport", &local_port); | |
| local_address = | |
| gst_structure_get_string (srtobject->parameters, "localaddress"); | |
| GST_OBJECT_UNLOCK (srtobject->element); | |
| /* According to SRT norm, bind local address and port if specified */ | |
| if (local_address != NULL && local_port != 0) { | |
| gpointer bind_sa; | |
| gsize bind_sa_len; | |
| GSocketAddress *bind_addr = | |
| gst_srt_object_resolve (srtobject, local_address, local_port, error); | |
| if (!bind_addr) { | |
| goto failed; | |
| } | |
| bind_sa_len = g_socket_address_get_native_size (bind_addr); | |
| bind_sa = g_alloca (bind_sa_len); | |
| if (!g_socket_address_to_native (bind_addr, bind_sa, bind_sa_len, error)) { | |
| g_clear_object (&bind_addr); | |
| goto failed; | |
| } | |
| g_clear_object (&bind_addr); | |
| GST_DEBUG_OBJECT (srtobject->element, "Binding to %s (port: %d)", | |
| local_address, local_port); | |
| if (srt_bind (sock, bind_sa, bind_sa_len) == SRT_ERROR) { | |
| g_set_error (error, GST_RESOURCE_ERROR, | |
| GST_RESOURCE_ERROR_OPEN_READ_WRITE, "Cannot bind to %s:%d - %s", | |
| local_address, local_port, srt_getlasterror_str ()); | |
| goto failed; | |
| } | |
| } | |
| if (srt_epoll_add_usock (srtobject->poll_id, sock, &sock_flags) < 0) { | |
| g_set_error (error, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_SETTINGS, "%s", | |
| srt_getlasterror_str ()); | |
| goto failed; | |
| } | |
| if (srt_connect (sock, sa, sa_len) == SRT_ERROR) { | |
| g_set_error (error, GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_OPEN_READ, "%s", | |
| srt_getlasterror_str ()); | |
| srt_epoll_remove_usock (srtobject->poll_id, sock); | |
| goto failed; | |
| } | |
| srtobject->sock = sock; | |
| return TRUE; | |
| failed: | |
| srt_close (sock); | |
| return FALSE; | |
| } | |
| static gboolean | |
| gst_srt_object_open_internal (GstSRTObject * srtobject, GError ** error) | |
| { | |
| GSocketAddress *socket_address = NULL; | |
| GstSRTConnectionMode connection_mode; | |
| gpointer sa; | |
| size_t sa_len; | |
| const gchar *addr_str; | |
| guint port; | |
| gboolean ret = FALSE; | |
| GST_OBJECT_LOCK (srtobject->element); | |
| if (!gst_structure_get_enum (srtobject->parameters, | |
| "mode", GST_TYPE_SRT_CONNECTION_MODE, (gint *) & connection_mode)) { | |
| GST_WARNING_OBJECT (srtobject->element, | |
| "Cannot get connection mode information." " Use default mode"); | |
| connection_mode = GST_SRT_DEFAULT_MODE; | |
| } | |
| addr_str = gst_uri_get_host (srtobject->uri); | |
| if (addr_str == NULL) { | |
| connection_mode = GST_SRT_CONNECTION_MODE_LISTENER; | |
| addr_str = GST_SRT_DEFAULT_LOCALADDRESS; | |
| GST_DEBUG_OBJECT (srtobject->element, | |
| "Given uri doesn't have hostname or address. Use any (%s) and" | |
| " setting listener mode", addr_str); | |
| } | |
| port = gst_uri_get_port (srtobject->uri); | |
| GST_DEBUG_OBJECT (srtobject->element, | |
| "Opening SRT socket with parameters: %" GST_PTR_FORMAT, | |
| srtobject->parameters); | |
| GST_OBJECT_UNLOCK (srtobject->element); | |
| socket_address = gst_srt_object_resolve (srtobject, addr_str, port, error); | |
| if (socket_address == NULL) { | |
| goto out; | |
| } | |
| sa_len = g_socket_address_get_native_size (socket_address); | |
| sa = g_alloca (sa_len); | |
| if (!g_socket_address_to_native (socket_address, sa, sa_len, error)) { | |
| goto out; | |
| } | |
| if (connection_mode == GST_SRT_CONNECTION_MODE_LISTENER) { | |
| ret = gst_srt_object_wait_connect (srtobject, sa, sa_len, error); | |
| } else { | |
| ret = | |
| gst_srt_object_connect (srtobject, connection_mode, sa, sa_len, error); | |
| } | |
| out: | |
| g_clear_object (&socket_address); | |
| return ret; | |
| } | |
| gboolean | |
| gst_srt_object_open (GstSRTObject * srtobject, GError ** error) | |
| { | |
| GST_OBJECT_LOCK (srtobject->element); | |
| srtobject->opened = TRUE; | |
| GST_OBJECT_UNLOCK (srtobject->element); | |
| g_cancellable_reset (srtobject->cancellable); | |
| srtobject->bytes = 0; | |
| return gst_srt_object_open_internal (srtobject, error); | |
| } | |
| static void | |
| gst_srt_object_close_internal (GstSRTObject * srtobject) | |
| { | |
| g_mutex_lock (&srtobject->sock_lock); | |
| if (srtobject->sock != SRT_INVALID_SOCK) { | |
| srt_epoll_remove_usock (srtobject->poll_id, srtobject->sock); | |
| GST_DEBUG_OBJECT (srtobject->element, "Closing SRT socket (0x%x)", | |
| srtobject->sock); | |
| srt_close (srtobject->sock); | |
| srtobject->sock = SRT_INVALID_SOCK; | |
| } | |
| if (srtobject->thread) { | |
| GThread *thread = g_steal_pointer (&srtobject->thread); | |
| g_mutex_unlock (&srtobject->sock_lock); | |
| g_thread_join (thread); | |
| g_mutex_lock (&srtobject->sock_lock); | |
| } | |
| if (srtobject->callers) { | |
| GList *callers = g_steal_pointer (&srtobject->callers); | |
| g_list_foreach (callers, (GFunc) srt_caller_signal_removed, srtobject); | |
| g_list_free_full (callers, (GDestroyNotify) srt_caller_free); | |
| } | |
| srtobject->sent_headers = FALSE; | |
| g_mutex_unlock (&srtobject->sock_lock); | |
| } | |
| void | |
| gst_srt_object_close (GstSRTObject * srtobject) | |
| { | |
| GST_OBJECT_LOCK (srtobject->element); | |
| srtobject->opened = FALSE; | |
| GST_OBJECT_UNLOCK (srtobject->element); | |
| g_cancellable_cancel (srtobject->cancellable); | |
| gst_srt_object_close_internal (srtobject); | |
| } | |
| static gboolean | |
| gst_srt_object_wait_caller (GstSRTObject * srtobject) | |
| { | |
| gboolean ret; | |
| g_mutex_lock (&srtobject->sock_lock); | |
| ret = (srtobject->callers != NULL); | |
| if (!ret) { | |
| GST_INFO_OBJECT (srtobject->element, "Waiting for connection"); | |
| while (!ret && !g_cancellable_is_cancelled (srtobject->cancellable)) { | |
| g_cond_wait (&srtobject->sock_cond, &srtobject->sock_lock); | |
| ret = (srtobject->callers != NULL); | |
| } | |
| } | |
| g_mutex_unlock (&srtobject->sock_lock); | |
| if (ret) { | |
| GST_DEBUG_OBJECT (srtobject->element, "Got a connection"); | |
| } | |
| return ret; | |
| } | |
| gssize | |
| gst_srt_object_read (GstSRTObject * srtobject, guint8 * data, gsize size, | |
| GError ** error, SRT_MSGCTRL * mctrl) | |
| { | |
| gssize len = 0; | |
| gint poll_timeout; | |
| GstSRTConnectionMode connection_mode = GST_SRT_CONNECTION_MODE_NONE; | |
| gint poll_id = SRT_ERROR; | |
| SRTSOCKET sock = SRT_INVALID_SOCK; | |
| gboolean auto_reconnect; | |
| GError *internal_error = NULL; | |
| /* Only source element can read data */ | |
| g_return_val_if_fail (gst_uri_handler_get_uri_type (GST_URI_HANDLER | |
| (srtobject->element)) == GST_URI_SRC, -1); | |
| GST_OBJECT_LOCK (srtobject->element); | |
| gst_structure_get_enum (srtobject->parameters, "mode", | |
| GST_TYPE_SRT_CONNECTION_MODE, (gint *) & connection_mode); | |
| if (!gst_structure_get_int (srtobject->parameters, "poll-timeout", | |
| &poll_timeout)) { | |
| poll_timeout = GST_SRT_DEFAULT_POLL_TIMEOUT; | |
| } | |
| auto_reconnect = srtobject->auto_reconnect; | |
| GST_OBJECT_UNLOCK (srtobject->element); | |
| retry: | |
| if (connection_mode == GST_SRT_CONNECTION_MODE_LISTENER) { | |
| if (!gst_srt_object_wait_caller (srtobject)) | |
| return 0; | |
| g_mutex_lock (&srtobject->sock_lock); | |
| if (srtobject->callers) { | |
| SRTCaller *caller = srtobject->callers->data; | |
| poll_id = caller->poll_id; | |
| sock = caller->sock; | |
| } | |
| g_mutex_unlock (&srtobject->sock_lock); | |
| if (poll_id == SRT_ERROR) | |
| return 0; | |
| } else { | |
| poll_id = srtobject->poll_id; | |
| sock = srtobject->sock; | |
| } | |
| while (!g_cancellable_is_cancelled (srtobject->cancellable)) { | |
| SRTSOCKET rsock, wsock; | |
| gint rsocklen = 1, wsocklen = 1; | |
| SYSSOCKET rsys, wsys; | |
| gint rsyslen = 1, wsyslen = 1; | |
| gint ret; | |
| switch (srt_getsockstate (sock)) { | |
| case SRTS_BROKEN: | |
| case SRTS_CLOSING: | |
| case SRTS_CLOSED: | |
| case SRTS_NONEXIST: | |
| g_set_error (&internal_error, GST_RESOURCE_ERROR, | |
| GST_RESOURCE_ERROR_READ, "Socket is broken or closed"); | |
| goto err; | |
| default: | |
| break; | |
| } | |
| GST_TRACE_OBJECT (srtobject->element, "Waiting for read"); | |
| ret = | |
| srt_epoll_wait (poll_id, &rsock, &rsocklen, &wsock, &wsocklen, | |
| poll_timeout, &rsys, &rsyslen, &wsys, &wsyslen); | |
| if (g_cancellable_is_cancelled (srtobject->cancellable)) | |
| break; | |
| if (ret < 0) { | |
| gint srt_errno = srt_getlasterror (NULL); | |
| if (srt_errno == SRT_ETIMEOUT) | |
| continue; | |
| g_set_error (&internal_error, GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_READ, | |
| "Failed to poll socket: %s", srt_getlasterror_str ()); | |
| goto err; | |
| } | |
| if (rsocklen != 1) | |
| continue; | |
| if (wsocklen == 1 && rsocklen == 1) { | |
| /* Socket reported in wsock AND rsock signifies an error. */ | |
| gint reason = srt_getrejectreason (wsock); | |
| if (reason == SRT_REJ_BADSECRET || reason == SRT_REJ_UNSECURE) { | |
| g_set_error (&internal_error, GST_RESOURCE_ERROR, | |
| GST_RESOURCE_ERROR_NOT_AUTHORIZED, | |
| "Failed to authenticate: %" REASON_FORMAT, REASON_ARGS (reason)); | |
| /* Failure to authenticate are fatal */ | |
| auto_reconnect = FALSE; | |
| } else { | |
| g_set_error (&internal_error, GST_RESOURCE_ERROR, | |
| GST_RESOURCE_ERROR_READ, | |
| "Error on SRT socket: %" REASON_FORMAT, REASON_ARGS (reason)); | |
| } | |
| goto err; | |
| } | |
| srt_msgctrl_init (mctrl); | |
| len = srt_recvmsg2 (rsock, (char *) (data), size, mctrl); | |
| if (len == SRT_ERROR) { | |
| gint srt_errno = srt_getlasterror (NULL); | |
| if (srt_errno == SRT_EASYNCRCV) | |
| continue; | |
| g_set_error (&internal_error, GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_READ, | |
| "Failed to receive from SRT socket: %s", srt_getlasterror_str ()); | |
| goto err; | |
| } | |
| srtobject->bytes += len; | |
| break; | |
| } | |
| return len; | |
| err: | |
| if (g_cancellable_is_cancelled (srtobject->cancellable)) | |
| return 0; | |
| if (connection_mode == GST_SRT_CONNECTION_MODE_LISTENER) { | |
| /* Caller has disappeared. */ | |
| ERROR_TO_WARNING (srtobject, internal_error, ""); | |
| g_clear_error (&internal_error); | |
| return 0; | |
| } | |
| if (!auto_reconnect) { | |
| g_propagate_error (error, internal_error); | |
| return -1; | |
| } | |
| ERROR_TO_WARNING (srtobject, internal_error, ". Trying to reconnect"); | |
| g_clear_error (&internal_error); | |
| gst_srt_object_close_internal (srtobject); | |
| if (!gst_srt_object_open_internal (srtobject, error)) { | |
| return -1; | |
| } | |
| goto retry; | |
| } | |
| void | |
| gst_srt_object_unlock (GstSRTObject * srtobject) | |
| { | |
| GST_DEBUG_OBJECT (srtobject->element, "waking up SRT"); | |
| /* connection is only waited for in listener mode, | |
| * but there is no harm in raising signal in any case */ | |
| g_mutex_lock (&srtobject->sock_lock); | |
| /* however, a race might be harmful ... | |
| * the cancellation is used as 'flushing' flag here, | |
| * so make sure it is so detected by the intended part at proper time */ | |
| g_cancellable_cancel (srtobject->cancellable); | |
| g_cond_signal (&srtobject->sock_cond); | |
| g_mutex_unlock (&srtobject->sock_lock); | |
| } | |
| void | |
| gst_srt_object_unlock_stop (GstSRTObject * srtobject) | |
| { | |
| g_cancellable_reset (srtobject->cancellable); | |
| } | |
| static gboolean | |
| gst_srt_object_send_headers (GstSRTObject * srtobject, SRTSOCKET sock, | |
| gint poll_id, gint poll_timeout, GstBufferList * headers, GError ** error) | |
| { | |
| guint size, i; | |
| if (!headers) | |
| return TRUE; | |
| size = gst_buffer_list_length (headers); | |
| GST_DEBUG_OBJECT (srtobject->element, "Sending %u stream headers", size); | |
| for (i = 0; i < size; i++) { | |
| SRTSOCKET wsock = sock; | |
| gint wsocklen = 1; | |
| SYSSOCKET rsys, wsys; | |
| gint rsyslen = 1, wsyslen = 1; | |
| gint ret = 0, sent; | |
| GstBuffer *buffer = gst_buffer_list_get (headers, i); | |
| GstMapInfo mapinfo; | |
| if (g_cancellable_is_cancelled (srtobject->cancellable)) | |
| break; | |
| if (poll_id >= 0) { | |
| switch (srt_getsockstate (sock)) { | |
| case SRTS_BROKEN: | |
| case SRTS_CLOSING: | |
| case SRTS_CLOSED: | |
| case SRTS_NONEXIST: | |
| g_set_error (error, GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_WRITE, | |
| "Socket is broken or closed"); | |
| return FALSE; | |
| default: | |
| break; | |
| } | |
| GST_TRACE_OBJECT (srtobject->element, "Waiting for header write"); | |
| ret = | |
| srt_epoll_wait (poll_id, NULL, 0, &wsock, &wsocklen, poll_timeout, | |
| &rsys, &rsyslen, &wsys, &wsyslen); | |
| if (g_cancellable_is_cancelled (srtobject->cancellable)) | |
| break; | |
| } | |
| if (ret < 0) { | |
| gint srt_errno = srt_getlasterror (NULL); | |
| if (srt_errno == SRT_ETIMEOUT) | |
| continue; | |
| g_set_error (error, GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_WRITE, | |
| "Failed to poll socket: %s", srt_getlasterror_str ()); | |
| return FALSE; | |
| } | |
| if (wsocklen != 1) | |
| continue; | |
| GST_TRACE_OBJECT (srtobject->element, "sending header %u %" GST_PTR_FORMAT, | |
| i, buffer); | |
| if (!gst_buffer_map (buffer, &mapinfo, GST_MAP_READ)) { | |
| g_set_error (error, GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_WRITE, | |
| "Failed to map header buffer"); | |
| return FALSE; | |
| } | |
| sent = srt_sendmsg2 (wsock, (char *) mapinfo.data, mapinfo.size, 0); | |
| if (sent == SRT_ERROR) { | |
| g_set_error (error, GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_WRITE, "%s", | |
| srt_getlasterror_str ()); | |
| gst_buffer_unmap (buffer, &mapinfo); | |
| return FALSE; | |
| } | |
| srtobject->bytes += sent; | |
| gst_buffer_unmap (buffer, &mapinfo); | |
| } | |
| return TRUE; | |
| } | |
| static gssize | |
| gst_srt_object_write_to_callers (GstSRTObject * srtobject, | |
| GstBufferList * headers, const GstMapInfo * mapinfo) | |
| { | |
| GList *item, *next; | |
| g_mutex_lock (&srtobject->sock_lock); | |
| for (item = srtobject->callers, next = NULL; item; item = next) { | |
| SRTCaller *caller = item->data; | |
| gssize len = 0; | |
| const guint8 *msg = mapinfo->data; | |
| gint sent; | |
| gint payload_size, optlen = sizeof (payload_size); | |
| next = item->next; | |
| if (g_cancellable_is_cancelled (srtobject->cancellable)) { | |
| goto cancelled; | |
| } | |
| if (!caller->sent_headers) { | |
| GError *error = NULL; | |
| if (!gst_srt_object_send_headers (srtobject, caller->sock, -1, 0, headers, | |
| &error)) { | |
| GST_WARNING_OBJECT (srtobject->element, | |
| "Failed to send headers to caller %d: %s", caller->sock, | |
| error->message); | |
| g_error_free (error); | |
| goto err; | |
| } | |
| caller->sent_headers = TRUE; | |
| } | |
| if (srt_getsockflag (caller->sock, SRTO_PAYLOADSIZE, &payload_size, | |
| &optlen)) { | |
| GST_WARNING_OBJECT (srtobject->element, "%s", srt_getlasterror_str ()); | |
| goto err; | |
| } | |
| while (len < mapinfo->size) { | |
| gint rest = MIN (mapinfo->size - len, payload_size); | |
| sent = srt_sendmsg2 (caller->sock, (char *) (msg + len), rest, 0); | |
| if (sent < 0) { | |
| GST_WARNING_OBJECT (srtobject->element, "Dropping caller %d: %s", | |
| caller->sock, srt_getlasterror_str ()); | |
| goto err; | |
| } | |
| len += sent; | |
| srtobject->bytes += sent; | |
| } | |
| continue; | |
| err: | |
| srtobject->callers = g_list_delete_link (srtobject->callers, item); | |
| srt_caller_signal_removed (caller, srtobject); | |
| srt_caller_free (caller); | |
| } | |
| g_mutex_unlock (&srtobject->sock_lock); | |
| return mapinfo->size; | |
| cancelled: | |
| g_mutex_unlock (&srtobject->sock_lock); | |
| return 0; | |
| } | |
| static gssize | |
| gst_srt_object_write_one (GstSRTObject * srtobject, GstBufferList * headers, | |
| const GstMapInfo * mapinfo, GError ** error) | |
| { | |
| gssize len = 0; | |
| gint poll_timeout; | |
| const guint8 *msg = mapinfo->data; | |
| gint payload_size, optlen = sizeof (payload_size); | |
| gboolean wait_for_connection, auto_reconnect; | |
| GError *internal_error = NULL; | |
| GST_OBJECT_LOCK (srtobject->element); | |
| wait_for_connection = srtobject->wait_for_connection; | |
| auto_reconnect = srtobject->auto_reconnect; | |
| if (!gst_structure_get_int (srtobject->parameters, "poll-timeout", | |
| &poll_timeout)) { | |
| poll_timeout = GST_SRT_DEFAULT_POLL_TIMEOUT; | |
| } | |
| GST_OBJECT_UNLOCK (srtobject->element); | |
| retry: | |
| if (!srtobject->sent_headers) { | |
| if (!gst_srt_object_send_headers (srtobject, srtobject->sock, | |
| srtobject->poll_id, poll_timeout, headers, &internal_error)) { | |
| goto err; | |
| } | |
| srtobject->sent_headers = TRUE; | |
| } | |
| while (len < mapinfo->size) { | |
| SRTSOCKET rsock, wsock; | |
| gint rsocklen = 1, wsocklen = 1; | |
| SYSSOCKET rsys, wsys; | |
| gint rsyslen = 1, wsyslen = 1; | |
| gint ret, sent, rest; | |
| gboolean connecting_but_not_waiting = FALSE; | |
| if (g_cancellable_is_cancelled (srtobject->cancellable)) | |
| break; | |
| switch (srt_getsockstate (srtobject->sock)) { | |
| case SRTS_BROKEN: | |
| case SRTS_CLOSING: | |
| case SRTS_CLOSED: | |
| case SRTS_NONEXIST: | |
| g_set_error (&internal_error, GST_RESOURCE_ERROR, | |
| GST_RESOURCE_ERROR_WRITE, "Socket is broken or closed"); | |
| goto err; | |
| case SRTS_CONNECTING: | |
| if (!wait_for_connection) { | |
| /* We need to check for SRT_EPOLL_ERR */ | |
| connecting_but_not_waiting = TRUE; | |
| } | |
| break; | |
| default: | |
| break; | |
| } | |
| GST_TRACE_OBJECT (srtobject->element, "Waiting a write"); | |
| ret = | |
| srt_epoll_wait (srtobject->poll_id, &rsock, &rsocklen, &wsock, | |
| &wsocklen, connecting_but_not_waiting ? 0 : poll_timeout, &rsys, | |
| &rsyslen, &wsys, &wsyslen); | |
| if (g_cancellable_is_cancelled (srtobject->cancellable)) | |
| break; | |
| if (ret < 0) { | |
| gint srt_errno = srt_getlasterror (NULL); | |
| if (srt_errno == SRT_ETIMEOUT) | |
| continue; | |
| g_set_error (&internal_error, GST_RESOURCE_ERROR, | |
| GST_RESOURCE_ERROR_WRITE, "Failed to poll socket: %s", | |
| srt_getlasterror_str ()); | |
| goto err; | |
| } | |
| if (wsocklen != 1) | |
| continue; | |
| if (wsocklen == 1 && rsocklen == 1) { | |
| /* Socket reported in wsock AND rsock signifies an error. */ | |
| gint reason = srt_getrejectreason (wsock); | |
| if (reason == SRT_REJ_BADSECRET || reason == SRT_REJ_UNSECURE) { | |
| g_set_error (&internal_error, GST_RESOURCE_ERROR, | |
| GST_RESOURCE_ERROR_NOT_AUTHORIZED, | |
| "Failed to authenticate: %" REASON_FORMAT, REASON_ARGS (reason)); | |
| } else { | |
| g_set_error (&internal_error, GST_RESOURCE_ERROR, | |
| GST_RESOURCE_ERROR_WRITE, | |
| "Error on SRT socket: %" REASON_FORMAT, REASON_ARGS (reason)); | |
| } | |
| goto err; | |
| } | |
| if (connecting_but_not_waiting) { | |
| GST_LOG_OBJECT (srtobject->element, | |
| "Not connected yet. Dropping the buffer."); | |
| break; | |
| } | |
| if (srt_getsockflag (wsock, SRTO_PAYLOADSIZE, &payload_size, &optlen)) { | |
| g_set_error (&internal_error, GST_RESOURCE_ERROR, | |
| GST_RESOURCE_ERROR_WRITE, "%s", srt_getlasterror_str ()); | |
| goto err; | |
| } | |
| rest = MIN (mapinfo->size - len, payload_size); | |
| sent = srt_sendmsg2 (wsock, (char *) (msg + len), rest, 0); | |
| if (sent < 0) { | |
| g_set_error (&internal_error, GST_RESOURCE_ERROR, | |
| GST_RESOURCE_ERROR_WRITE, "%s", srt_getlasterror_str ()); | |
| goto err; | |
| } | |
| len += sent; | |
| srtobject->bytes += sent; | |
| continue; | |
| } | |
| return len; | |
| err: | |
| if (g_cancellable_is_cancelled (srtobject->cancellable)) | |
| return 0; | |
| if (!auto_reconnect) { | |
| g_propagate_error (error, internal_error); | |
| return -1; | |
| } | |
| ERROR_TO_WARNING (srtobject, internal_error, ". Trying to reconnect"); | |
| g_clear_error (&internal_error); | |
| gst_srt_object_close_internal (srtobject); | |
| if (!gst_srt_object_open_internal (srtobject, error)) { | |
| return -1; | |
| } | |
| goto retry; | |
| } | |
| gssize | |
| gst_srt_object_write (GstSRTObject * srtobject, GstBufferList * headers, | |
| const GstMapInfo * mapinfo, GError ** error) | |
| { | |
| gssize len = 0; | |
| GstSRTConnectionMode connection_mode = GST_SRT_CONNECTION_MODE_NONE; | |
| gboolean wait_for_connection; | |
| /* Only sink element can write data */ | |
| g_return_val_if_fail (gst_uri_handler_get_uri_type (GST_URI_HANDLER | |
| (srtobject->element)) == GST_URI_SINK, -1); | |
| GST_OBJECT_LOCK (srtobject->element); | |
| gst_structure_get_enum (srtobject->parameters, "mode", | |
| GST_TYPE_SRT_CONNECTION_MODE, (gint *) & connection_mode); | |
| wait_for_connection = srtobject->wait_for_connection; | |
| GST_OBJECT_UNLOCK (srtobject->element); | |
| if (connection_mode == GST_SRT_CONNECTION_MODE_LISTENER) { | |
| if (wait_for_connection) { | |
| if (!gst_srt_object_wait_caller (srtobject)) | |
| return 0; | |
| } | |
| len = gst_srt_object_write_to_callers (srtobject, headers, mapinfo); | |
| } else { | |
| len = gst_srt_object_write_one (srtobject, headers, mapinfo, error); | |
| } | |
| return len; | |
| } | |
| static GstStructure * | |
| get_stats_for_srtsock (GstSRTObject * srtobject, SRTSOCKET srtsock) | |
| { | |
| GstStructure *s; | |
| int ret; | |
| SRT_TRACEBSTATS stats; | |
| ret = srt_bstats (srtsock, &stats, 0); | |
| if (ret < 0) { | |
| GST_WARNING_OBJECT (srtobject->element, | |
| "failed to retrieve stats for socket %d (reason %s)", | |
| srtsock, srt_getlasterror_str ()); | |
| return NULL; | |
| } | |
| s = gst_structure_new ("application/x-srt-statistics", | |
| /* number of sent data packets, including retransmissions */ | |
| "packets-sent", G_TYPE_INT64, stats.pktSent, | |
| /* number of lost packets (sender side) */ | |
| "packets-sent-lost", G_TYPE_INT, stats.pktSndLoss, | |
| /* number of retransmitted packets */ | |
| "packets-retransmitted", G_TYPE_INT, stats.pktRetrans, | |
| /* number of received ACK packets */ | |
| "packet-ack-received", G_TYPE_INT, stats.pktRecvACK, | |
| /* number of received NAK packets */ | |
| "packet-nack-received", G_TYPE_INT, stats.pktRecvNAK, | |
| /* time duration when UDT is sending data (idle time exclusive) */ | |
| "send-duration-us", G_TYPE_INT64, stats.usSndDuration, | |
| /* number of sent data bytes, including retransmissions */ | |
| "bytes-sent", G_TYPE_UINT64, stats.byteSent, | |
| /* number of retransmitted bytes */ | |
| "bytes-retransmitted", G_TYPE_UINT64, stats.byteRetrans, | |
| /* number of too-late-to-send dropped bytes */ | |
| "bytes-sent-dropped", G_TYPE_UINT64, stats.byteSndDrop, | |
| /* number of too-late-to-send dropped packets */ | |
| "packets-sent-dropped", G_TYPE_INT, stats.pktSndDrop, | |
| /* sending rate in Mb/s */ | |
| "send-rate-mbps", G_TYPE_DOUBLE, stats.mbpsSendRate, | |
| /* busy sending time (i.e., idle time exclusive) */ | |
| "send-duration-us", G_TYPE_UINT64, stats.usSndDuration, | |
| "negotiated-latency-ms", G_TYPE_INT, stats.msSndTsbPdDelay, | |
| "packets-received", G_TYPE_INT64, stats.pktRecvTotal, | |
| "packets-received-lost", G_TYPE_INT, stats.pktRcvLossTotal, | |
| /* number of retransmitted packets registered at receiver side */ | |
| "packets-received-retransmitted", G_TYPE_INT, stats.pktRcvRetrans, | |
| /* number of dropped packets by the receiver */ | |
| "packets-received-dropped", G_TYPE_INT, stats.pktRcvDropTotal, | |
| /* number of sent ACK packets */ | |
| "packet-ack-sent", G_TYPE_INT, stats.pktSentACK, | |
| /* number of sent NAK packets */ | |
| "packet-nack-sent", G_TYPE_INT, stats.pktSentNAK, | |
| "bytes-received", G_TYPE_UINT64, stats.byteRecvTotal, | |
| "bytes-received-lost", G_TYPE_UINT64, stats.byteRcvLossTotal, | |
| "receive-rate-mbps", G_TYPE_DOUBLE, stats.mbpsRecvRate, | |
| "negotiated-latency-ms", G_TYPE_INT, stats.msRcvTsbPdDelay, | |
| /* estimated bandwidth, in Mb/s */ | |
| "bandwidth-mbps", G_TYPE_DOUBLE, stats.mbpsBandwidth, | |
| "rtt-ms", G_TYPE_DOUBLE, stats.msRTT, NULL); | |
| GST_DEBUG_OBJECT (srtobject->element, | |
| "retreived stats for socket %d: %" GST_PTR_FORMAT, srtsock, s); | |
| return s; | |
| } | |
| GstStructure * | |
| gst_srt_object_get_stats (GstSRTObject * srtobject) | |
| { | |
| GstStructure *s = NULL; | |
| gboolean is_sender = GST_IS_BASE_SINK (srtobject->element); | |
| g_mutex_lock (&srtobject->sock_lock); | |
| if (srtobject->thread == NULL) { | |
| /* Not a listening socket */ | |
| s = get_stats_for_srtsock (srtobject, srtobject->sock); | |
| } | |
| if (s == NULL) { | |
| s = gst_structure_new_empty ("application/x-srt-statistics"); | |
| } | |
| if (srtobject->callers) { | |
| GValueArray *callers_stats = g_value_array_new (1); | |
| GValue callers_stats_v = G_VALUE_INIT; | |
| GList *item, *next; | |
| for (item = srtobject->callers, next = NULL; item; item = next) { | |
| SRTCaller *caller = item->data; | |
| GstStructure *tmp; | |
| GValue *v; | |
| next = item->next; | |
| tmp = get_stats_for_srtsock (srtobject, caller->sock); | |
| if (tmp == NULL) { | |
| srtobject->callers = g_list_delete_link (srtobject->callers, item); | |
| srt_caller_signal_removed (caller, srtobject); | |
| srt_caller_free (caller); | |
| continue; | |
| } | |
| gst_structure_set (tmp, "caller-address", G_TYPE_SOCKET_ADDRESS, | |
| caller->sockaddr, NULL); | |
| g_value_array_append (callers_stats, NULL); | |
| v = g_value_array_get_nth (callers_stats, callers_stats->n_values - 1); | |
| g_value_init (v, GST_TYPE_STRUCTURE); | |
| g_value_take_boxed (v, tmp); | |
| } | |
| g_value_init (&callers_stats_v, G_TYPE_VALUE_ARRAY); | |
| g_value_take_boxed (&callers_stats_v, callers_stats); | |
| gst_structure_take_value (s, "callers", &callers_stats_v); | |
| } | |
| gst_structure_set (s, is_sender ? "bytes-sent-total" : "bytes-received-total", | |
| G_TYPE_UINT64, srtobject->bytes, NULL); | |
| g_mutex_unlock (&srtobject->sock_lock); | |
| return s; | |
| } |