From 967f30d688d2cfaef69bf6e523b0d2d79d9bed3d Mon Sep 17 00:00:00 2001 From: Evangelos Ribeiro Tzaras Date: Wed, 17 Feb 2021 23:48:33 +0100 Subject: [PATCH] sip: Add media manager and sipify origin * pipeline: we should bind the used socket of our udpsink to the socket udpsrc --- plugins/sip/calls-sip-call.c | 93 +++- plugins/sip/calls-sip-call.h | 15 +- plugins/sip/calls-sip-media-manager.c | 127 +++++ plugins/sip/calls-sip-media-manager.h | 45 ++ plugins/sip/calls-sip-media-pipeline.c | 625 +++++++++++++++++++++++++ plugins/sip/calls-sip-media-pipeline.h | 39 ++ plugins/sip/calls-sip-origin.c | 596 +++++++++++++++++++++-- plugins/sip/calls-sip-origin.h | 4 +- plugins/sip/calls-sip-util.c | 55 +++ plugins/sip/calls-sip-util.h | 4 + plugins/sip/gst-rfc3551.c | 61 +++ plugins/sip/gst-rfc3551.h | 47 ++ plugins/sip/meson.build | 6 +- 13 files changed, 1669 insertions(+), 48 deletions(-) create mode 100644 plugins/sip/calls-sip-media-manager.c create mode 100644 plugins/sip/calls-sip-media-manager.h create mode 100644 plugins/sip/calls-sip-media-pipeline.c create mode 100644 plugins/sip/calls-sip-media-pipeline.h create mode 100644 plugins/sip/calls-sip-util.c create mode 100644 plugins/sip/gst-rfc3551.c create mode 100644 plugins/sip/gst-rfc3551.h diff --git a/plugins/sip/calls-sip-call.c b/plugins/sip/calls-sip-call.c index 7dee92b..dcc3047 100644 --- a/plugins/sip/calls-sip-call.c +++ b/plugins/sip/calls-sip-call.c @@ -25,9 +25,12 @@ #include "calls-sip-call.h" #include "calls-message-source.h" +#include "calls-sip-media-manager.h" +#include "calls-sip-media-pipeline.h" #include "calls-call.h" #include +#include struct _CallsSipCall @@ -36,6 +39,10 @@ struct _CallsSipCall gchar *number; gboolean inbound; CallsCallState state; + + CallsSipMediaManager *manager; + CallsSipMediaPipeline *pipeline; + nua_handle_t *nh; }; static void calls_sip_call_message_source_interface_init (CallsCallInterface *iface); @@ -49,6 +56,7 @@ G_DEFINE_TYPE_WITH_CODE (CallsSipCall, calls_sip_call, G_TYPE_OBJECT, enum { PROP_0, + PROP_CALL_HANDLE, PROP_CALL_NUMBER, PROP_CALL_INBOUND, PROP_CALL_STATE, @@ -97,6 +105,8 @@ answer (CallsCall *call) return; } + /* need to include SDP answer here */ + change_state (self, CALLS_CALL_STATE_ACTIVE); } @@ -135,6 +145,10 @@ calls_sip_call_set_property (GObject *object, CallsSipCall *self = CALLS_SIP_CALL (object); switch (property_id) { + case PROP_CALL_HANDLE: + self->nh = g_value_get_pointer (value); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -167,6 +181,10 @@ calls_sip_call_get_property (GObject *object, g_value_set_string (value, NULL); break; + case PROP_CALL_HANDLE: + g_value_set_pointer (value, self->nh); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -181,6 +199,10 @@ calls_sip_call_finalize (GObject *object) g_free (self->number); + if (self->pipeline) { + calls_sip_media_pipeline_stop (self->pipeline); + g_clear_object (&self->pipeline); + } G_OBJECT_CLASS (calls_sip_call_parent_class)->finalize (object); } @@ -194,6 +216,13 @@ calls_sip_call_class_init (CallsSipCallClass *klass) object_class->set_property = calls_sip_call_set_property; object_class->finalize = calls_sip_call_finalize; + props[PROP_CALL_HANDLE] = + g_param_spec_pointer ("nua-handle", + "NUA handle", + "The used NUA handler", + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + g_object_class_install_property (object_class, PROP_CALL_HANDLE, props[PROP_CALL_HANDLE]); + #define IMPLEMENTS(ID, NAME) \ g_object_class_override_property (object_class, ID, NAME); \ props[ID] = g_object_class_find_property(object_class, NAME); @@ -226,18 +255,76 @@ calls_sip_call_message_source_interface_init (CallsCallInterface *iface) static void calls_sip_call_init (CallsSipCall *self) { + MediaCodecInfo *best_codec; + + self->manager = calls_sip_media_manager_default (); + + best_codec = get_best_codec (self->manager); + + self->pipeline = calls_sip_media_pipeline_new (best_codec); +} + + +void +calls_sip_call_setup_local_media (CallsSipCall *self, + guint port_rtp, + guint port_rtcp) +{ + g_return_if_fail (CALLS_IS_SIP_CALL (self)); + g_return_if_fail (CALLS_IS_SIP_MEDIA_PIPELINE (self->pipeline)); + + g_debug ("Setting local ports: RTP/RTCP %u/%u", port_rtp, port_rtcp); + g_object_set (G_OBJECT (self->pipeline), + "lport-rtp", port_rtp, + "lport-rtcp", port_rtcp, + NULL); +} + + +void +calls_sip_call_setup_remote_media (CallsSipCall *self, + const char *remote, + guint port_rtp, + guint port_rtcp) +{ + g_return_if_fail (CALLS_IS_SIP_CALL (self)); + g_return_if_fail (CALLS_IS_SIP_MEDIA_PIPELINE (self->pipeline)); + + g_debug ("Setting remote ports: RTP/RTCP %u/%u", port_rtp, port_rtcp); + g_object_set (G_OBJECT (self->pipeline), + "remote", remote, + "rport-rtp", port_rtp, + "rport-rtcp", port_rtcp, + NULL); +} + + +void +calls_sip_call_activate_media (CallsSipCall *self, + gboolean enabled) +{ + g_return_if_fail (CALLS_IS_SIP_CALL (self)); + + if (enabled) { + ; + } else { + ; + } } CallsSipCall * -calls_sip_call_new (const gchar *number, - gboolean inbound) +calls_sip_call_new (const gchar *number, + gboolean inbound, + nua_handle_t *handle) { CallsSipCall *call; g_return_val_if_fail (number != NULL, NULL); - call = g_object_new (CALLS_TYPE_SIP_CALL, NULL); + call = g_object_new (CALLS_TYPE_SIP_CALL, + "nua-handle", handle, + NULL); call->number = g_strdup (number); call->inbound = inbound; diff --git a/plugins/sip/calls-sip-call.h b/plugins/sip/calls-sip-call.h index 3eebb19..7558653 100644 --- a/plugins/sip/calls-sip-call.h +++ b/plugins/sip/calls-sip-call.h @@ -25,6 +25,7 @@ #pragma once #include +#include G_BEGIN_DECLS @@ -32,7 +33,17 @@ G_BEGIN_DECLS G_DECLARE_FINAL_TYPE (CallsSipCall, calls_sip_call, CALLS, SIP_CALL, GObject); -CallsSipCall *calls_sip_call_new (const gchar *number, - gboolean inbound); +CallsSipCall *calls_sip_call_new (const gchar *number, + gboolean inbound, + nua_handle_t *handle); +void calls_sip_call_setup_remote_media (CallsSipCall *self, + const char *remote, + guint port_rtp, + guint port_rtcp); +void calls_sip_call_setup_local_media (CallsSipCall *self, + guint port_rtp, + guint port_rtcp); +void calls_sip_call_activate_media (CallsSipCall *self, + gboolean enabled); G_END_DECLS diff --git a/plugins/sip/calls-sip-media-manager.c b/plugins/sip/calls-sip-media-manager.c new file mode 100644 index 0000000..07ab589 --- /dev/null +++ b/plugins/sip/calls-sip-media-manager.c @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2021 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Evangelos Ribeiro Tzaras + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#define G_LOG_DOMAIN "CallsCallsSipMediaManager" + +#include "calls-sip-media-pipeline.h" +#include "gst-rfc3551.h" +#include "calls-sip-media-manager.h" + +#include + +typedef struct _CallsSipMediaManager +{ + GObject parent; +} CallsSipMediaManager; + +G_DEFINE_TYPE (CallsSipMediaManager, calls_sip_media_manager, G_TYPE_OBJECT); + + +MediaCodecInfo* +get_best_codec (CallsSipMediaManager *self) +{ + return media_codec_by_name ("PCMU"); +} + + +static void +calls_sip_media_manager_finalize (GObject *object) +{ + gst_deinit (); + + G_OBJECT_CLASS (calls_sip_media_manager_parent_class)->finalize (object); +} + + +static void +calls_sip_media_manager_class_init (CallsSipMediaManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = calls_sip_media_manager_finalize; +} + + +static void +calls_sip_media_manager_init (CallsSipMediaManager *self) +{ + gst_init (NULL, NULL); +} + + +/* Public functions */ + +CallsSipMediaManager * +calls_sip_media_manager_default () +{ + static CallsSipMediaManager *instance = NULL; + + if (instance == NULL) { + g_debug ("Creating CallsSipMediaManager"); + instance = g_object_new (CALLS_TYPE_SIP_MEDIA_MANAGER, NULL); + g_object_add_weak_pointer (G_OBJECT (instance), (gpointer *) &instance); + } + return instance; +} + + +/* calls_sip_media_manager_static_capabilities: + * + * @port: Should eventually come from the ICE stack + * @use_srtp: Whether to use srtp (not really handled) + * + * Returns: (full-control) string describing capabilities + * to be used in the session description (SDP) + */ +char * +calls_sip_media_manager_static_capabilities (CallsSipMediaManager *self, + guint port, + gboolean use_srtp) +{ + char *attribute_line = "rtpmap:0 PCMU/8000"; + char *payload_type = use_srtp ? "SAVP" : "AVP"; + g_autofree char *media_line = NULL; + + g_return_val_if_fail (CALLS_IS_SIP_MEDIA_MANAGER (self), NULL); + + media_line = g_strdup_printf ("audio %d RTP/%s 0", port, payload_type); + + /* TODO we can have multiple attribute lines (or media lines for that matter) */ + /* TODO add attribute describing RTCP stream */ + return g_strdup_printf ("v=0\r\n" + "m=%s\r\n" + "a=%s\r\n", + media_line, + attribute_line); +} + + +/* TODO lookup plugins in GStreamer */ +gboolean +calls_sip_media_manager_supports_media (CallsSipMediaManager *self, + const char *media_type) +{ + return TRUE; +} + diff --git a/plugins/sip/calls-sip-media-manager.h b/plugins/sip/calls-sip-media-manager.h new file mode 100644 index 0000000..2a9cb1a --- /dev/null +++ b/plugins/sip/calls-sip-media-manager.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2021 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Evangelos Ribeiro Tzaras + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#pragma once + +#include "gst-rfc3551.h" + +#include +#include + +#define CALLS_TYPE_SIP_MEDIA_MANAGER (calls_sip_media_manager_get_type ()) + +G_DECLARE_FINAL_TYPE (CallsSipMediaManager, calls_sip_media_manager, CALLS, SIP_MEDIA_MANAGER, GObject) + + +CallsSipMediaManager* calls_sip_media_manager_default (void); +gchar* calls_sip_media_manager_static_capabilities (CallsSipMediaManager *self, + guint port, + gboolean use_srtp); +gboolean calls_sip_media_manager_supports_media (CallsSipMediaManager *self, + const char *media_type); +void calls_sip_media_manager_activate_media (CallsSipMediaManager *self); +void calls_sip_media_manager_deactivate_media (CallsSipMediaManager *self); +MediaCodecInfo* get_best_codec (CallsSipMediaManager *self); diff --git a/plugins/sip/calls-sip-media-pipeline.c b/plugins/sip/calls-sip-media-pipeline.c new file mode 100644 index 0000000..46f81cd --- /dev/null +++ b/plugins/sip/calls-sip-media-pipeline.c @@ -0,0 +1,625 @@ +/* + * Copyright (C) 2021 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Evangelos Ribeiro Tzaras + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#define G_LOG_DOMAIN "CallsSipMediaPipeline" + +#include "calls-sip-media-pipeline.h" + +#include +#include + +struct _CallsSipMediaPipeline { + GObject parent; + + MediaCodecInfo *codec; + /* Connection details */ + char *remote; + + gint rport_rtp; + gint lport_rtp; + + gint rport_rtcp; + gint lport_rtcp; + + gboolean is_running; + + /* Gstreamer Elements (sending) */ + GstElement *send_pipeline; + GstElement *audiosrc; + GstElement *send_rtpbin; + GstElement *rtp_sink; /* UDP out */ + GstElement *payloader; + GstElement *encoder; + GstElement *rtcp_send_sink; + GstElement *rtcp_send_src; + /* Gstreamer elements (receiving) */ + GstElement *recv_pipeline; + GstElement *audiosink; + GstElement *recv_rtpbin; + GstElement *rtp_src; /* UDP in */ + GstElement *depayloader; + GstElement *decoder; + GstElement *rtcp_recv_sink; + GstElement *rtcp_recv_src; + + /* Gstreamer busses */ + GstBus *bus_send; + GstBus *bus_recv; + guint bus_watch_send; + guint bus_watch_recv; +}; + + +static void initable_iface_init (GInitableIface *iface); + + +G_DEFINE_TYPE_WITH_CODE (CallsSipMediaPipeline, calls_sip_media_pipeline, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init)); + +enum { + PROP_0, + PROP_CODEC, + PROP_REMOTE, + PROP_LPORT_RTP, + PROP_RPORT_RTP, + PROP_LPORT_RTCP, + PROP_RPORT_RTCP, + PROP_LAST_PROP, +}; +static GParamSpec *props[PROP_LAST_PROP]; + + +/* rtpbin adds a pad once the payload is verified */ +static void +pad_added_cb (GstElement *rtpbin, + GstPad *srcpad, + GstElement *depayloader) +{ + GstPad *sinkpad; + /* there might still be another rtp src bin linked to the depayloader */ + //GstPad *other_srcpad; + + g_debug ("pad added: %s", GST_PAD_NAME (srcpad)); + + sinkpad = gst_element_get_static_pad (depayloader, "sink"); + if (gst_pad_link (srcpad, sinkpad) != GST_PAD_LINK_OK) + g_error ("Failed to link rtpbin to depayloader"); + + gst_object_unref (sinkpad); +} + + +static gboolean +bus_cb (GstBus *bus, + GstMessage *message, + gpointer data) +{ + CallsSipMediaPipeline *pipeline = CALLS_SIP_MEDIA_PIPELINE (data); + + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_ERROR: + { + g_autoptr (GError) error = NULL; + g_autofree char *msg = NULL; + + gst_message_parse_error (message, &error, &msg); + g_error ("Error: %s", msg); + break; + } + + case GST_MESSAGE_WARNING: + { + g_autoptr (GError) error = NULL; + g_autofree char *msg = NULL; + + gst_message_parse_warning (message, &error, &msg); + g_warning ("Warning: %s", msg); + break; + } + + case GST_MESSAGE_EOS: + g_debug ("Received end of stream"); + calls_sip_media_pipeline_stop (pipeline); + break; + + case GST_MESSAGE_STATE_CHANGED: + { + GstState oldstate; + GstState newstate; + + gst_message_parse_state_changed (message, &oldstate, &newstate, NULL); + g_debug ("Element %s has changed state from %s to %s", + GST_OBJECT_NAME (message->src), + gst_element_state_get_name (oldstate), + gst_element_state_get_name (newstate)); + break; + } + + default: + g_debug ("Got unhandled %s message", GST_MESSAGE_TYPE_NAME (message)); + break; + } + + /* keep watching for messages on the bus */ + return TRUE; +} + + +static void +get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + CallsSipMediaPipeline *self = CALLS_SIP_MEDIA_PIPELINE (object); + + switch (property_id) { + case PROP_CODEC: + g_value_set_pointer (value, self->codec); + break; + + case PROP_REMOTE: + g_value_set_string (value, self->remote); + break; + + case PROP_LPORT_RTP: + g_value_set_uint (value, self->lport_rtp); + break; + + case PROP_LPORT_RTCP: + g_value_set_uint (value, self->lport_rtcp); + break; + + case PROP_RPORT_RTP: + g_value_set_uint (value, self->rport_rtp); + break; + + case PROP_RPORT_RTCP: + g_value_set_uint (value, self->rport_rtcp); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + + +static void +set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + CallsSipMediaPipeline *self = CALLS_SIP_MEDIA_PIPELINE (object); + + switch (property_id) { + case PROP_CODEC: + self->codec = g_value_get_pointer (value); + break; + + case PROP_REMOTE: + g_free (self->remote); + self->remote = g_value_dup_string (value); + break; + + case PROP_LPORT_RTP: + self->lport_rtp = g_value_get_uint (value); + break; + + case PROP_LPORT_RTCP: + self->lport_rtcp = g_value_get_uint (value); + break; + + case PROP_RPORT_RTP: + self->rport_rtp = g_value_get_uint (value); + break; + + case PROP_RPORT_RTCP: + self->rport_rtcp = g_value_get_uint (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + + +static void +calls_sip_media_pipeline_class_init (CallsSipMediaPipelineClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->set_property = set_property; + object_class->get_property = get_property; + + /* Maybe we want to turn Codec into a GObject later */ + props[PROP_CODEC] = g_param_spec_pointer ("codec", + "Codec", + "Media codec", + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); + + props[PROP_REMOTE] = g_param_spec_string ("remote", + "Remote", + "Remote host", + NULL, + G_PARAM_READWRITE); + + props[PROP_LPORT_RTP] = g_param_spec_uint ("lport-rtp", + "lport-rtp", + "local rtp port", + 1025, 65535, 5002, + G_PARAM_READWRITE); + + props[PROP_LPORT_RTCP] = g_param_spec_uint ("lport-rtcp", + "lport-rtcp", + "local rtcp port", + 1025, 65535, 5003, + G_PARAM_READWRITE); + + props[PROP_RPORT_RTP] = g_param_spec_uint ("rport-rtp", + "rport-rtp", + "remote rtp port", + 1025, 65535, 5002, + G_PARAM_READWRITE); + + props[PROP_RPORT_RTCP] = g_param_spec_uint ("rport-rtcp", + "rport-rtcp", + "remote rtcp port", + 1025, 65535, 5003, + G_PARAM_READWRITE); + + g_object_class_install_properties (object_class, PROP_LAST_PROP, props); +} + + +static void +calls_sip_media_pipeline_init (CallsSipMediaPipeline *self) +{ +} + + +static gboolean +initable_init (GInitable *initable, + GCancellable *cancelable, + GError **error) +{ + CallsSipMediaPipeline *self = CALLS_SIP_MEDIA_PIPELINE (initable); + GstCaps *caps; + g_autofree char *caps_string = NULL; + GstPad *srcpad, *sinkpad; + + /* could also use autoaudiosink instead of pulsesink */ + self->audiosink = gst_element_factory_make ("pulsesink", "sink"); + self->audiosrc = gst_element_factory_make ("pulsesrc", "source"); + if (!self->audiosrc || !self->audiosink) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Could not create audiosink or audiosrc"); + return FALSE; + } + + /* maybe we need to also explicitly add audioconvert and audioresample elements */ + self->send_rtpbin = gst_element_factory_make ("rtpbin", "send-rtpbin"); + self->recv_rtpbin = gst_element_factory_make ("rtpbin", "recv-rtpbin"); + if (!self->send_rtpbin || !self->recv_rtpbin) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Could not create send/receive rtpbin"); + return FALSE; + } + + self->decoder = gst_element_factory_make (self->codec->gst_decoder_name, "decoder"); + if (!self->decoder) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Could not create decoder %s", self->codec->gst_decoder_name); + return FALSE; + } + + self->depayloader = gst_element_factory_make (self->codec->gst_depayloader_name, "depayloader"); + if (!self->depayloader) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Could not create depayloader %s", self->codec->gst_depayloader_name); + return FALSE; + } + + self->encoder = gst_element_factory_make (self->codec->gst_encoder_name, "encoder"); + if (!self->encoder) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Could not create encoder %s", self->codec->gst_encoder_name); + return FALSE; + } + + self->payloader = gst_element_factory_make (self->codec->gst_payloader_name, "payloader"); + if (!self->encoder) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Could not create payloader %s", self->codec->gst_payloader_name); + return FALSE; + } + + self->rtp_src = gst_element_factory_make ("udpsrc", "rtp-udp-src"); + self->rtp_sink = gst_element_factory_make ("udpsink", "rtp-udp-sink"); + self->rtcp_recv_sink = gst_element_factory_make ("udpsink", "rtcp-udp-recv-sink"); + self->rtcp_recv_src = gst_element_factory_make ("udpsrc", "rtcp-udp-recv-src"); + self->rtcp_send_sink = gst_element_factory_make ("udpsink", "rtcp-udp-send-sink"); + self->rtcp_send_src = gst_element_factory_make ("udpsrc", "rtcp-udp-send-src"); + + if (!self->rtp_src || !self->rtp_sink || + !self->rtcp_recv_sink || !self->rtcp_recv_src || + !self->rtcp_send_sink || !self->rtcp_send_src) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Could not create udp sinks or sources"); + return FALSE; + } + + self->send_pipeline = gst_pipeline_new ("rtp-send-pipeline"); + self->recv_pipeline = gst_pipeline_new ("rtp-recv-pipeline"); + + if (!self->send_pipeline || !self->recv_pipeline) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Could not create send or receiver pipeline"); + return FALSE; + } + + gst_object_ref_sink (self->send_pipeline); + gst_object_ref_sink (self->recv_pipeline); + +/* get the busses and establish watches */ + self->bus_send = gst_pipeline_get_bus (GST_PIPELINE (self->send_pipeline)); + self->bus_recv = gst_pipeline_get_bus (GST_PIPELINE (self->recv_pipeline)); + self->bus_watch_send = gst_bus_add_watch (self->bus_send, bus_cb, self); + self->bus_watch_recv = gst_bus_add_watch (self->bus_recv, bus_cb, self); + + gst_bin_add_many (GST_BIN (self->recv_pipeline), self->depayloader, self->decoder, + self->audiosink, NULL); + gst_bin_add_many (GST_BIN (self->send_pipeline), self->payloader, self->encoder, + self->audiosrc, NULL); + + if (!gst_element_link_many (self->depayloader, self->decoder, self->audiosink, NULL)) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Failed to link depayloader decoder and audiosink"); + return FALSE; + } + + if (!gst_element_link_many (self->audiosrc, self->encoder, self->payloader, NULL)) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Failed to link audiosrc encoder and payloader"); + return FALSE; + } + + gst_bin_add (GST_BIN (self->send_pipeline), self->send_rtpbin); + gst_bin_add (GST_BIN (self->recv_pipeline), self->recv_rtpbin); + + gst_bin_add_many (GST_BIN (self->send_pipeline), self->rtp_sink, + self->rtcp_send_src, self->rtcp_send_sink, NULL); + gst_bin_add_many (GST_BIN (self->recv_pipeline), self->rtp_src, + self->rtcp_recv_src, self->rtcp_recv_sink, NULL); + + caps_string = media_codec_get_gst_capabilities (self->codec); + g_debug ("Capabilities:\n%s", caps_string); + + caps = gst_caps_from_string (caps_string); + + /* set udp sinks and sources for RTP and RTCP */ + g_object_set (self->rtp_src, + "caps", caps, + NULL); + + g_object_set (self->rtcp_recv_sink, + "async", FALSE, + "sync", FALSE, + NULL); + + g_object_set (self->rtcp_send_sink, + "async", FALSE, + "sync", FALSE, + NULL); + + /* bind to properties of udp sinks and sources */ + /* Receiver side */ + if (self->remote == NULL) + self->remote = g_strdup ("localhost"); + + g_object_bind_property (self, "lport-rtp", + self->rtp_src, "port", + G_BINDING_BIDIRECTIONAL); + + g_object_bind_property (self, "lport-rtcp", + self->rtcp_recv_src, "port", + G_BINDING_BIDIRECTIONAL); + + g_object_bind_property (self, "rport-rtcp", + self->rtcp_recv_sink, "port", + G_BINDING_BIDIRECTIONAL); + + g_object_bind_property (self, "remote", + self->rtcp_recv_sink, "host", + G_BINDING_BIDIRECTIONAL); + + /* Sender side */ + g_object_bind_property (self, "rport-rtp", + self->rtp_sink, "port", + G_BINDING_BIDIRECTIONAL); + + g_object_bind_property (self, "remote", + self->rtp_sink, "host", + G_BINDING_BIDIRECTIONAL); + + g_object_bind_property (self, "lport-rtcp", + self->rtcp_send_src, "port", + G_BINDING_BIDIRECTIONAL); + + g_object_bind_property (self, "rport-rtcp", + self->rtcp_send_sink, "port", + G_BINDING_BIDIRECTIONAL); + + g_object_bind_property (self, "remote", + self->rtcp_send_sink, "host", + G_BINDING_BIDIRECTIONAL); + + /* TODO https://sources.debian.org/src/gst-plugins-good1.0/1.18.3-1/gst/rtsp/gstrtspsrc.c/?hl=4542#L4542 */ + + /* Link pads */ + /* in/receive direction */ + /* request and link the pads */ + srcpad = gst_element_get_static_pad (self->rtp_src, "src"); + sinkpad = gst_element_get_request_pad (self->recv_rtpbin, "recv_rtp_sink_0"); + if (gst_pad_link (srcpad, sinkpad) != GST_PAD_LINK_OK) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Failed to link rtpsrc to rtpbin"); + return FALSE; + } + gst_object_unref (srcpad); + gst_object_unref (sinkpad); + + srcpad = gst_element_get_static_pad (self->rtcp_recv_src, "src"); + sinkpad = gst_element_get_request_pad (self->recv_rtpbin, "recv_rtcp_sink_0"); + if (gst_pad_link (srcpad, sinkpad) != GST_PAD_LINK_OK) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Failed to link rtcpsrc to rtpbin"); + return FALSE; + } + gst_object_unref (srcpad); + gst_object_unref (sinkpad); + + srcpad = gst_element_get_request_pad (self->recv_rtpbin, "send_rtcp_src_0"); + sinkpad = gst_element_get_static_pad (self->rtcp_recv_sink, "sink"); + if (gst_pad_link (srcpad, sinkpad) != GST_PAD_LINK_OK) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Failed to link rtpbin to rtcpsink"); + return FALSE; + } + gst_object_unref (srcpad); + gst_object_unref (sinkpad); + + /* need to link RTP pad to the depayloader */ + g_signal_connect (self->recv_rtpbin, "pad-added", G_CALLBACK (pad_added_cb), self->depayloader); + + + /* out/send direction */ + /* link payloader src to RTP sink pad */ + sinkpad = gst_element_get_request_pad (self->send_rtpbin, "send_rtp_sink_0"); + srcpad = gst_element_get_static_pad (self->payloader, "src"); + if (gst_pad_link (srcpad, sinkpad) != GST_PAD_LINK_OK) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Failed to link payloader to rtpbin"); + return FALSE; + } + gst_object_unref (srcpad); + gst_object_unref (sinkpad); + + /* link RTP srcpad to udpsink */ + srcpad = gst_element_get_static_pad (self->send_rtpbin, "send_rtp_src_0"); + sinkpad = gst_element_get_static_pad (self->rtp_sink, "sink"); + if (gst_pad_link (srcpad, sinkpad) != GST_PAD_LINK_OK) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Failed to link rtpbin to rtpsink"); + return FALSE; + } + gst_object_unref (srcpad); + gst_object_unref (sinkpad); + + /* RTCP srcpad to udpsink */ + srcpad = gst_element_get_request_pad (self->send_rtpbin, "send_rtcp_src_0"); + sinkpad = gst_element_get_static_pad (self->rtcp_send_sink, "sink"); + if (gst_pad_link (srcpad, sinkpad) != GST_PAD_LINK_OK) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Failed to link rtpbin to rtcpsink"); + return FALSE; + } + gst_object_unref (srcpad); + gst_object_unref (sinkpad); + + /* receive RTCP */ + srcpad = gst_element_get_static_pad (self->rtcp_send_src, "src"); + sinkpad = gst_element_get_request_pad (self->send_rtpbin, "recv_rtcp_sink_0"); + if (gst_pad_link (srcpad, sinkpad) != GST_PAD_LINK_OK) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Failed to link rtcpsrc to rtpbin"); + return FALSE; + } + gst_object_unref (srcpad); + gst_object_unref (sinkpad); + + return TRUE; +} + + +static void +initable_iface_init (GInitableIface *iface) +{ + iface->init = initable_init; +} + + +CallsSipMediaPipeline* +calls_sip_media_pipeline_new (MediaCodecInfo *codec) +{ + CallsSipMediaPipeline *pipeline; + g_autoptr (GError) error = NULL; + + pipeline = g_initable_new (CALLS_TYPE_SIP_MEDIA_PIPELINE, NULL, &error, + "codec", codec, + NULL); + if (pipeline == NULL) + g_error ("Media pipeline could not be initialized: %s", error->message); + + return pipeline; +} + + +void +calls_sip_media_pipeline_start (CallsSipMediaPipeline *self) +{ + g_return_if_fail (CALLS_IS_SIP_MEDIA_PIPELINE (self)); + + g_debug ("Starting media pipeline"); + self->is_running = TRUE; + + gst_element_set_state (self->send_pipeline, GST_STATE_PLAYING); + gst_element_set_state (self->recv_pipeline, GST_STATE_PLAYING); +} + + +void +calls_sip_media_pipeline_stop (CallsSipMediaPipeline *self) +{ + g_return_if_fail (CALLS_IS_SIP_MEDIA_PIPELINE (self)); + + g_debug ("Stopping media pipeline"); + self->is_running = FALSE; + + gst_element_set_state (self->send_pipeline, GST_STATE_NULL); + gst_element_set_state (self->recv_pipeline, GST_STATE_NULL); +} + + +void +calls_sip_media_pipeline_pause (CallsSipMediaPipeline *self) +{ + g_return_if_fail (CALLS_IS_SIP_MEDIA_PIPELINE (self)); + + g_debug ("Pause/unpause media pipeline"); + self->is_running = FALSE; +} + diff --git a/plugins/sip/calls-sip-media-pipeline.h b/plugins/sip/calls-sip-media-pipeline.h new file mode 100644 index 0000000..7891034 --- /dev/null +++ b/plugins/sip/calls-sip-media-pipeline.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2021 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Evangelos Ribeiro Tzaras + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#pragma once + +#include "gst-rfc3551.h" + +#include + +#define CALLS_TYPE_SIP_MEDIA_PIPELINE (calls_sip_media_pipeline_get_type ()) + +G_DECLARE_FINAL_TYPE (CallsSipMediaPipeline, calls_sip_media_pipeline, CALLS, SIP_MEDIA_PIPELINE, GObject) + + +CallsSipMediaPipeline* calls_sip_media_pipeline_new (MediaCodecInfo *codec); +void calls_sip_media_pipeline_start (CallsSipMediaPipeline *self); +void calls_sip_media_pipeline_stop (CallsSipMediaPipeline *self); +void calls_sip_media_pipeline_pause (CallsSipMediaPipeline *self); diff --git a/plugins/sip/calls-sip-origin.c b/plugins/sip/calls-sip-origin.c index e8c13c0..32a80d3 100644 --- a/plugins/sip/calls-sip-origin.c +++ b/plugins/sip/calls-sip-origin.c @@ -28,10 +28,16 @@ #include "calls-origin.h" #include "calls-sip-call.h" #include "calls-sip-util.h" +#include "calls-sip-enums.h" +#include "calls-sip-media-manager.h" #include #include #include +#include +#include +#include +#include struct _CallsSipOrigin @@ -55,14 +61,18 @@ struct _CallsSipOrigin SipAccountState state; + CallsSipMediaManager *media_manager; + /* Account information */ gchar *user; gchar *password; gchar *host; + gchar *transport_protocol; + const gchar *protocol_prefix; gint port; - gchar *protocol; GList *calls; + GHashTable *call_handles; }; static void calls_sip_origin_message_source_interface_init (CallsOriginInterface *iface); @@ -84,35 +94,479 @@ enum { PROP_ACC_PROTOCOL, PROP_ACC_DIRECT, PROP_SIP_CONTEXT, + PROP_ACC_STATE, PROP_CALLS, PROP_LAST_PROP, }; static GParamSpec *props[PROP_LAST_PROP]; -static gboolean -init_sip_account (CallsSipOrigin *self) +static void +sip_authenticate (CallsSipOrigin *origin, + nua_handle_t *nh, + sip_t const *sip) { + const gchar *scheme = NULL; + const gchar *realm = NULL; + g_autofree gchar *auth = NULL; + sip_www_authenticate_t *www_auth = sip->sip_www_authenticate; + sip_proxy_authenticate_t *proxy_auth = sip->sip_proxy_authenticate; + + if (www_auth) { + scheme = www_auth->au_scheme; + realm = msg_params_find (www_auth->au_params, "realm="); + } + else if (proxy_auth) { + scheme = proxy_auth->au_scheme; + realm = msg_params_find (proxy_auth->au_params, "realm="); + } + g_debug ("need to authenticate to realm %s", realm); + + auth = g_strdup_printf ("%s:%s:%s:%s", + scheme, realm, origin->user, origin->password); + nua_authenticate (nh, NUTAG_AUTH (auth)); +} + + +static void +sip_r_invite (int status, + char const *phrase, + nua_t *nua, + CallsSipOrigin *origin, + nua_handle_t *nh, + CallsSipHandles *op, + sip_t const *sip, + tagi_t tags[]) +{ + g_debug ("response to outgoing INVITE: %03d %s", status, phrase); + + /* TODO call states (see i_state) */ + if (status == 401) { + sip_authenticate (origin, nh, sip); + } + else if (status == 403) { + g_warning ("wrong credentials?"); + } + else if (status == 407) { + sip_authenticate (origin, nh, sip); + } + else if (status == 904) { + g_warning ("unmatched challenge"); + } + else if (status == 180) { + } + else if (status == 100) { + } + else if (status == 200) { + } +} + + +static void +sip_r_register (int status, + char const *phrase, + nua_t *nua, + CallsSipOrigin *origin, + nua_handle_t *nh, + CallsSipHandles *op, + sip_t const *sip, + tagi_t tags[]) +{ + g_debug ("response to REGISTER: %03d %s", status, phrase); + + if (status == 200) { + g_debug ("REGISTER successful"); + + origin->state = SIP_ACCOUNT_ONLINE; + } + else if (status == 401) { + sip_authenticate (origin, nh, sip); + + origin->state = SIP_ACCOUNT_AUTHENTICATING; + } + else if (status == 403) { + g_warning ("wrong credentials?"); + origin->state = SIP_ACCOUNT_ERROR_RETRY; + } + else if (status == 904) { + g_warning ("unmatched challenge"); + origin->state = SIP_ACCOUNT_ERROR_RETRY; + } + g_object_notify_by_pspec (G_OBJECT (origin), props[PROP_ACC_STATE]); +} + + +static void +sip_i_state (int status, + char const *phrase, + nua_t *nua, + CallsSipOrigin *origin, + nua_handle_t *nh, + CallsSipHandles *op, + sip_t const *sip, + tagi_t tags[]) +{ + const sdp_session_t *r_sdp = NULL; + gint call_state = nua_callstate_init; + CallsCallState state; + CallsSipCall *call; + + g_return_if_fail (CALLS_IS_SIP_ORIGIN (origin)); + + call = g_hash_table_lookup (origin->call_handles, nh); + + g_return_if_fail (call != NULL); + + g_debug ("The call state has changed: %03d %s", status, phrase); + tl_gets (tags, + SOATAG_REMOTE_SDP_REF (r_sdp), + NUTAG_CALLSTATE_REF (call_state), + TAG_END ()); + + /* XXX making some assumptions about the received SDP message here... + * namely: that there is only the session wide connection c= line + * and no individual connections per media stream. + * also: rtcp port = rtp port + 1 + */ + if (r_sdp) { + calls_sip_call_setup_remote_media (call, + r_sdp->sdp_connection->c_address, + r_sdp->sdp_media->m_port, + r_sdp->sdp_media->m_port + 1); + } + + /* TODO use CallCallStates with g_object_set (notify!) */ + switch (call_state) { + case nua_callstate_init: + return; + + case nua_callstate_calling: + state = CALLS_CALL_STATE_DIALING; + break; + + case nua_callstate_received: + state = CALLS_CALL_STATE_INCOMING; + break; + + case nua_callstate_ready: + g_debug ("Call ready. Activating media pipeline"); + + calls_sip_call_activate_media (call, TRUE); + state = CALLS_CALL_STATE_ACTIVE; + break; + + case nua_callstate_terminated: + g_debug ("Call terminated. Deactivating media pipeline"); + + calls_sip_call_activate_media (call, FALSE); + state = CALLS_CALL_STATE_DISCONNECTED; + break; + + case nua_callstate_authenticating: + g_warning ("TODO Move authentication (INVITE) here"); + return; + + default: + return; + } + g_object_set (call, "state", state, NULL); + +} + + +static void +sip_callback (nua_event_t event, + int status, + char const *phrase, + nua_t *nua, + nua_magic_t *magic, + nua_handle_t *nh, + nua_hmagic_t *hmagic, + sip_t const *sip, + tagi_t tags[]) +{ + CallsSipOrigin *origin = CALLS_SIP_ORIGIN (magic); + /* op currently unused */ + CallsSipHandles *op = hmagic; + switch (event) { + case nua_i_invite: + /* This needs to be handled by CallsSipCall */ + //g_debug ("incoming call INVITE: %03d %s", status, phrase); + //origin->oper->incoming_call_handle = nh; + ///* We can only handle a single call */ + //if (origin->call_state != SIP_CALL_READY) { + // const char * from = NULL; + + // tl_gets (tags, SIPTAG_FROM_STR_REF (from), TAG_END ()); + + // g_debug ("Rejecting call from %s", from); + // nua_respond (nh, 486, NULL, TAG_END ()); + //} + break; + + case nua_r_invite: + sip_r_invite (status, + phrase, + nua, + origin, + nh, + op, + sip, + tags); + break; + + case nua_i_ack: + g_debug ("incoming ACK: %03d %s", status, phrase); + break; + + case nua_i_bye: + g_debug ("incoming BYE: %03d %s", status, phrase); + break; + + case nua_r_bye: + g_debug ("response to BYE: %03d %s", status, phrase); + break; + + case nua_r_register: + sip_r_register (status, + phrase, + nua, + origin, + nh, + op, + sip, + tags); + break; + + case nua_r_set_params: + g_debug ("response to set_params: %03d %s", status, phrase); + break; + + case nua_i_outbound: + g_debug ("status of outbound engine has changed: %03d %s", status, phrase); + break; + + case nua_i_state: + sip_i_state (status, + phrase, + nua, + origin, + nh, + op, + sip, + tags); + break; + + case nua_r_cancel: + g_debug ("response to CANCEL: %03d %s", status, phrase); + break; + + case nua_r_terminate: + break; + + case nua_r_shutdown: + /* see also deinit_sip () */ + g_debug ("response to nua_shutdown: %03d %s", status, phrase); + if (status == 200) + origin->is_nua_shutdown = TRUE; + break; + + /* Deprecated events */ + case nua_i_active: + break; + case nua_i_terminated: + break; + + default: + /* unknown event -> print out error message */ + g_warning ("unknown event %d: %03d %s", + event, + status, + phrase); + g_warning ("printing tags"); + tl_print(stdout, "", tags); + break; + } +} + + +static nua_t * +setup_nua (CallsSipOrigin *self) +{ + g_autofree gchar *address = NULL; + nua_t *nua; + gboolean use_sips; + + g_return_val_if_fail (CALLS_IS_SIP_ORIGIN (self), NULL); + + address = g_strconcat (self->protocol_prefix, ":", self->user, "@", self->host, NULL); + + use_sips = check_sips (address); + + // TODO URLs must be changed to accomodate IPv6 use case (later, not important right now) + // Note: This is why using hostname does not work! (do we need two nua contexts for ipv4 and ipv6?) + nua = nua_create (self->ctx->root, + sip_callback, + self, + NUTAG_USER_AGENT ("sofia-test/0.0.1"), + NUTAG_URL ("sip:0.0.0.0:5060"), + TAG_IF (use_sips, NUTAG_SIPS_URL ("sips:0.0.0.0:5060")), + NUTAG_M_USERNAME (self->user), + SIPTAG_FROM_STR (address), + NUTAG_ENABLEINVITE (1), + NUTAG_AUTOANSWER (0), + NUTAG_AUTOACK (1), + NUTAG_MEDIA_ENABLE (1), + TAG_NULL ()); + + return nua; +} + + +static CallsSipHandles * +setup_sip_handles (CallsSipOrigin *self) +{ + CallsSipHandles *oper; + + g_return_val_if_fail (CALLS_IS_SIP_ORIGIN (self), NULL); + + if (!(oper = su_zalloc (self->ctx->home, sizeof(CallsSipHandles)))) { + g_warning ("cannot create handle"); + return NULL; + } + + oper->context = self->ctx; + oper->register_handle = nua_handle (self->nua, self->oper, + NUTAG_REGISTRAR (self->host), + TAG_END ()); + oper->call_handle = NULL; + oper->incoming_call_handle = NULL; + + return oper; +} + + +static void +setup_account_for_direct_connection (CallsSipOrigin *self) +{ + g_return_if_fail (CALLS_IS_SIP_ORIGIN (self)); + + /* honour username, if previously set */ + if (self->user == NULL) + self->user = g_strdup (g_get_user_name ()); + + g_free (self->host); + self->host = g_strdup (g_get_host_name ()); + + g_free (self->password); + self->password = NULL; + + g_free (self->transport_protocol); + self->transport_protocol = g_strdup ("UDP"); + + self->protocol_prefix = get_protocol_prefix (self->transport_protocol); + + g_debug ("Notifying account changed:\n" + "user: %s\nhost URL: %s", self->user, self->host); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ACC_USER]); + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ACC_HOST]); +} + + +static gboolean +is_account_complete (CallsSipOrigin *self) +{ + g_return_val_if_fail (CALLS_IS_SIP_ORIGIN (self), FALSE); + + /* we need only need to check for password if needing to authenticate over a proxy/UAS */ + if (self->user == NULL || + (!self->use_direct_connection && self->password == NULL) || + self->host == NULL || + self->transport_protocol == NULL || + self->protocol_prefix == NULL) + return FALSE; + + return TRUE; +} + + +static gboolean +init_sip_account (CallsSipOrigin *self, + GError **error) +{ + gboolean recoverable = FALSE; + + g_return_val_if_fail (CALLS_IS_SIP_ORIGIN (self), FALSE); + + if (self->use_direct_connection && !is_account_complete (self)) { + g_debug ("Account not set yet. Using user and hostname"); + setup_account_for_direct_connection (self); + } + + if (!is_account_complete (self)) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Must have completed account setup before calling" + "init_sip_account ()" + "Try again when account is setup"); + recoverable = TRUE; + goto err; + } + + // setup_nua and setup_oper only after account data has been set + self->nua = setup_nua (self); + if (self->nua == NULL) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Failed setting up nua context"); + goto err; + } + + self->oper = setup_sip_handles (self); + if (self->oper == NULL) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Failed setting operation handles"); + goto err; + } + + /* In the case of a direct connection we're immediately good to go */ + if (self->use_direct_connection) + self->state = SIP_ACCOUNT_ONLINE; + else + self->state = SIP_ACCOUNT_OFFLINE; + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ACC_STATE]); + return TRUE; + + err: + self->state = recoverable ? SIP_ACCOUNT_ERROR_RETRY : SIP_ACCOUNT_ERROR; + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ACC_STATE]); return FALSE; } -static gboolean -protocol_is_valid (const gchar *protocol) -{ - return g_strcmp0 (protocol, "UDP") == 0 || - g_strcmp0 (protocol, "TLS") == 0; -} static void remove_call (CallsSipOrigin *self, - CallsCall *call, - const gchar *reason) + CallsCall *call, + const gchar *reason) { CallsOrigin *origin; + CallsSipCall *sip_call; + gboolean inbound; + nua_handle_t *nh; origin = CALLS_ORIGIN (self); + sip_call = CALLS_SIP_CALL (call); + self->calls = g_list_remove (self->calls, call); + g_object_get (sip_call, + "inbound", &inbound, + "nua-handle", &nh, + NULL); + + g_hash_table_remove (self->call_handles, nh); + nua_handle_unref (nh); + g_signal_emit_by_name (origin, "call-removed", call, reason); g_object_unref (G_OBJECT (call)); @@ -120,7 +574,8 @@ remove_call (CallsSipOrigin *self, static void -remove_calls (CallsSipOrigin *self, const gchar *reason) +remove_calls (CallsSipOrigin *self, + const gchar *reason) { CallsCall *call; GList *next; @@ -137,16 +592,14 @@ remove_calls (CallsSipOrigin *self, const gchar *reason) g_signal_emit_by_name (self, "call-removed", call, reason); g_object_unref (call); } + + g_hash_table_remove_all (self->call_handles); + + g_clear_pointer (&self->oper->call_handle, nua_handle_unref); + g_clear_pointer (&self->oper->incoming_call_handle, nua_handle_unref); } -struct DisconnectedData -{ - CallsSipOrigin *self; - CallsCall *call; -}; - - static void on_call_state_changed_cb (CallsSipOrigin *self, CallsCallState new_state, @@ -167,21 +620,41 @@ on_call_state_changed_cb (CallsSipOrigin *self, static void add_call (CallsSipOrigin *self, - const gchar *address, - gboolean inbound) + const gchar *address, + gboolean inbound, + nua_handle_t *handle) { CallsSipCall *sip_call; CallsCall *call; + g_autofree gchar *local_sdp = NULL; - sip_call = calls_sip_call_new (address, inbound); + sip_call = calls_sip_call_new (address, inbound, handle); g_assert (sip_call != NULL); + /* XXX dynamically get/probe free ports */ + calls_sip_call_setup_local_media (sip_call, 19042, 19043); + + local_sdp = calls_sip_media_manager_static_capabilities (self->media_manager, + 19042, + check_sips (address)); + + g_assert (local_sdp); + g_debug ("Setting local SDP to string:\n%s", local_sdp); + + nua_set_params (self->nua, + SOATAG_USER_SDP_STR (local_sdp), + SOATAG_AF (SOA_AF_IP4_IP6), + TAG_END ()); + + self->oper->call_handle = handle; + call = CALLS_CALL (sip_call); g_signal_connect_swapped (call, "state-changed", G_CALLBACK (on_call_state_changed_cb), self); self->calls = g_list_append (self->calls, sip_call); + g_hash_table_insert (self->call_handles, handle, sip_call); g_signal_emit_by_name (CALLS_ORIGIN (self), "call-added", call); } @@ -191,6 +664,7 @@ static void dial (CallsOrigin *origin, const gchar *address) { + CallsSipOrigin *self; g_assert (CALLS_ORIGIN (origin)); g_assert (CALLS_IS_SIP_ORIGIN (origin)); @@ -200,7 +674,17 @@ dial (CallsOrigin *origin, return; } - add_call (CALLS_SIP_ORIGIN (origin), address, FALSE); + self = CALLS_SIP_ORIGIN (origin); + + if (self->oper->call_handle) + nua_handle_unref (self->oper->call_handle); + + self->oper->call_handle = nua_handle (self->nua, self->oper, + NUTAG_MEDIA_ENABLE (1), + SOATAG_ACTIVE_AUDIO (SOA_ACTIVE_SENDRECV), + TAG_END ()); + + add_call (CALLS_SIP_ORIGIN (origin), address, FALSE, self->oper->call_handle); } @@ -237,12 +721,13 @@ calls_sip_origin_set_property (GObject *object, if (!protocol_is_valid (g_value_get_string (value))) { g_warning ("Tried setting invalid protocol: '%s'\n" "Continue using old protocol: '%s'", - g_value_get_string (value), self->protocol); + g_value_get_string (value), self->transport_protocol); return; } - g_free (self->protocol); - self->protocol = g_value_dup_string (value); + g_free (self->transport_protocol); + self->transport_protocol = g_value_dup_string (value); + self->protocol_prefix = get_protocol_prefix (self->transport_protocol); break; case PROP_ACC_DIRECT: @@ -253,6 +738,10 @@ calls_sip_origin_set_property (GObject *object, self->ctx = g_value_get_pointer (value); break; + case PROP_ACC_STATE: + g_warning ("Setting the account state does not yet have any effect"); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -290,7 +779,11 @@ calls_sip_origin_get_property (GObject *object, break; case PROP_ACC_PROTOCOL: - g_value_set_string (value, self->protocol); + g_value_set_string (value, self->transport_protocol); + break; + + case PROP_ACC_STATE: + g_value_set_enum (value, self->state); break; default: @@ -304,8 +797,13 @@ static void calls_sip_origin_constructed (GObject *object) { CallsSipOrigin *self = CALLS_SIP_ORIGIN (object); + g_autoptr (GError) error = NULL; - init_sip_account (self); + if (!init_sip_account (self, &error)) { + g_warning ("Error initializing the SIP account: %s", error->message); + } + + self->media_manager = calls_sip_media_manager_default (); G_OBJECT_CLASS (calls_sip_origin_parent_class)->constructed (object); } @@ -354,7 +852,8 @@ calls_sip_origin_finalize (GObject *object) g_free (self->user); g_free (self->password); g_free (self->host); - g_free (self->protocol); + g_free (self->transport_protocol); + g_hash_table_destroy (self->call_handles); G_OBJECT_CLASS (calls_sip_origin_parent_class)->finalize (object); } @@ -425,6 +924,15 @@ calls_sip_origin_class_init (CallsSipOriginClass *klass) G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY); g_object_class_install_property (object_class, PROP_SIP_CONTEXT, props[PROP_SIP_CONTEXT]); + props[PROP_ACC_STATE] = + g_param_spec_enum ("account-state", + "Account state", + "The state of the SIP account", + SIP_TYPE_ACCOUNT_STATE, + SIP_ACCOUNT_NULL, + G_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_ACC_STATE, props[PROP_ACC_STATE]); + #define IMPLEMENTS(ID, NAME) \ g_object_class_override_property (object_class, ID, NAME); \ props[ID] = g_object_class_find_property(object_class, NAME); @@ -454,6 +962,8 @@ calls_sip_origin_init (CallsSipOrigin *self) { self->name = g_string_new (NULL); + self->call_handles = g_hash_table_new (NULL, NULL); + /* Direct connection mode is useful for debugging purposes */ self->use_direct_connection = TRUE; @@ -462,12 +972,13 @@ calls_sip_origin_init (CallsSipOrigin *self) void calls_sip_origin_create_inbound (CallsSipOrigin *self, - const gchar *address) + const gchar *address, + nua_handle_t *handle) { g_return_if_fail (address != NULL); g_return_if_fail (CALLS_IS_SIP_ORIGIN (self)); - add_call (self, address, TRUE); + add_call (self, address, TRUE, handle); } @@ -481,16 +992,19 @@ calls_sip_origin_new (const gchar *name, const gchar *protocol, gboolean direct_connection) { - CallsSipOrigin *origin = - g_object_new (CALLS_TYPE_SIP_ORIGIN, - "sip-context", sip_context, - "user", user, - "password", password, - "host", host, - "port", port, - "protocol", protocol, - "direct-connection", direct_connection, - NULL); + CallsSipOrigin *origin; + + g_return_val_if_fail (sip_context != NULL, NULL); + + origin = g_object_new (CALLS_TYPE_SIP_ORIGIN, + "sip-context", sip_context, + "user", user, + "password", password, + "host", host, + "port", port, + "protocol", protocol, + "direct-connection", direct_connection, + NULL); g_string_assign (origin->name, name); diff --git a/plugins/sip/calls-sip-origin.h b/plugins/sip/calls-sip-origin.h index f84faa8..73b8642 100644 --- a/plugins/sip/calls-sip-origin.h +++ b/plugins/sip/calls-sip-origin.h @@ -27,6 +27,7 @@ #include "calls-sip-util.h" #include +#include G_BEGIN_DECLS @@ -43,6 +44,7 @@ CallsSipOrigin *calls_sip_origin_new (const gchar *na const gchar *protocol, gboolean direct_connection); void calls_sip_origin_create_inbound (CallsSipOrigin *self, - const gchar *number); + const gchar *number, + nua_handle_t *handle); G_END_DECLS diff --git a/plugins/sip/calls-sip-util.c b/plugins/sip/calls-sip-util.c new file mode 100644 index 0000000..20534d1 --- /dev/null +++ b/plugins/sip/calls-sip-util.c @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2021 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Evangelos Ribeiro Tzaras + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include "calls-sip-util.h" + +gboolean +check_sips (const gchar *addr) +{ + /* To keep it simple we only check if the URL starts with "sips:" */ + return g_str_has_prefix (addr, "sips:"); +} + + +const gchar * +get_protocol_prefix (const gchar *protocol) +{ + if (g_strcmp0 (protocol, "UDP") == 0 || + g_strcmp0 (protocol, "TCP") == 0) + return "sip"; + + if (g_strcmp0 (protocol, "TLS") == 0) + return "sips"; + + return NULL; +} + + +gboolean +protocol_is_valid (const gchar *protocol) +{ + return g_strcmp0 (protocol, "UDP") == 0 || + g_strcmp0 (protocol, "TCP") == 0 || + g_strcmp0 (protocol, "TLS") == 0; +} diff --git a/plugins/sip/calls-sip-util.h b/plugins/sip/calls-sip-util.h index a2f96ce..9bf47c6 100644 --- a/plugins/sip/calls-sip-util.h +++ b/plugins/sip/calls-sip-util.h @@ -79,3 +79,7 @@ typedef enum SIP_ACCOUNT_ONLINE, } SipAccountState; + +gboolean check_sips (const gchar *addr); +const gchar *get_protocol_prefix (const gchar *protocol); +gboolean protocol_is_valid (const gchar *protocol); diff --git a/plugins/sip/gst-rfc3551.c b/plugins/sip/gst-rfc3551.c new file mode 100644 index 0000000..4a9d886 --- /dev/null +++ b/plugins/sip/gst-rfc3551.c @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2021 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Evangelos Ribeiro Tzaras + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include "gst-rfc3551.h" + +#include + +/* TODO check available codecs during runtime */ +static MediaCodecInfo gst_codecs[] = { + {0, "PCMU", 8000, 1, "rtppcmupay", "rtppcmudepay", "mulawenc", "mulawdec"}, + {3, "GSM", 8000, 1, "rtpgsmpay", "rtpgsmdepay", "gsmenc", "gsmdec"}, + {4, "G723", 8000, 1, "rtpg723pay", "rtpg723depay", "avenc_g723_1", "avdec_g723_1"}, // does not seem to work + {8, "PCMA", 8000, 1, "rtppcmapay", "rtppcmadepay", "alawenc", "alawdec"}, + {9, "G722", 8000, 1, "rtpg722pay", "rtpg722depay", "avenc_g722", "avdec_g722"}, +}; + + + +MediaCodecInfo * +media_codec_by_name (const char *name) +{ + g_return_val_if_fail (name != NULL, NULL); + + for (guint i = 0; i < G_N_ELEMENTS (gst_codecs); i++) { + if (g_strcmp0 (name, gst_codecs[i].name) == 0) + return &gst_codecs[i]; + } + + return NULL; +} + +gchar * +media_codec_get_gst_capabilities (MediaCodecInfo *codec) +{ + return g_strdup_printf ("application/x-rtp,media=(string)audio,clock-rate=(int)%d" + ",encoding-name=(string)%s,payload=(int)%d", + codec->clock_rate, + codec->name, + codec->payload_type); +} diff --git a/plugins/sip/gst-rfc3551.h b/plugins/sip/gst-rfc3551.h new file mode 100644 index 0000000..5ded2ee --- /dev/null +++ b/plugins/sip/gst-rfc3551.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2021 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Evangelos Ribeiro Tzaras + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#pragma once + +#include + +/* + * For more information + * see: https://tools.ietf.org/html/rfc3551#section-6 + */ + +typedef struct { + gint payload_type; + gchar* name; + gint clock_rate; + gint channels; + char *gst_payloader_name; + char *gst_depayloader_name; + char *gst_encoder_name; + char *gst_decoder_name; +} MediaCodecInfo; + + +MediaCodecInfo* media_codec_by_name (const char *name); +gchar* media_codec_get_gst_capabilities (MediaCodecInfo *codec); diff --git a/plugins/sip/meson.build b/plugins/sip/meson.build index 4196f93..bec73ed 100644 --- a/plugins/sip/meson.build +++ b/plugins/sip/meson.build @@ -44,7 +44,11 @@ sip_sources = files( [ 'calls-sip-call.c', 'calls-sip-call.h', 'calls-sip-origin.c', 'calls-sip-origin.h', - 'calls-sip-provider.c', 'calls-sip-provider.h' + 'calls-sip-provider.c', 'calls-sip-provider.h', + 'calls-sip-util.c', 'calls-sip-util.h', + 'calls-sip-media-manager.c', 'calls-sip-media-manager.h', + 'calls-sip-media-pipeline.c', 'calls-sip-media-pipeline.h', + 'gst-rfc3551.c', 'gst-rfc3551.h', ] )