summaryrefslogtreecommitdiff
path: root/protocols/JabberG
diff options
context:
space:
mode:
authorGeorge Hazan <ghazan@miranda.im>2022-08-03 21:02:36 +0300
committerGeorge Hazan <ghazan@miranda.im>2022-08-03 21:02:36 +0300
commit5323a782c4e8c42781f22ce2f488962a18f82554 (patch)
treef71537197b16f0f8fd0d6937f7120d018d220814 /protocols/JabberG
parent50acf9d37183f86f6f623aad410003392b0af41f (diff)
Jabber: initial version of Jingle support
Diffstat (limited to 'protocols/JabberG')
-rw-r--r--protocols/JabberG/jabber.vcxproj9
-rw-r--r--protocols/JabberG/jabber.vcxproj.filters3
-rw-r--r--protocols/JabberG/src/jabber_caps.cpp8
-rw-r--r--protocols/JabberG/src/jabber_caps.h9
-rw-r--r--protocols/JabberG/src/jabber_menu.cpp17
-rw-r--r--protocols/JabberG/src/jabber_opt.cpp3
-rw-r--r--protocols/JabberG/src/jabber_proto.cpp1
-rw-r--r--protocols/JabberG/src/jabber_proto.h16
-rw-r--r--protocols/JabberG/src/jabber_thread.cpp97
-rw-r--r--protocols/JabberG/src/jabber_voip.cpp519
10 files changed, 649 insertions, 33 deletions
diff --git a/protocols/JabberG/jabber.vcxproj b/protocols/JabberG/jabber.vcxproj
index 4d882b075d..cffe651bac 100644
--- a/protocols/JabberG/jabber.vcxproj
+++ b/protocols/JabberG/jabber.vcxproj
@@ -25,6 +25,11 @@
<ImportGroup Label="PropertySheets">
<Import Project="$(ProjectDir)..\..\build\vc.common\plugin.props" />
</ImportGroup>
+ <ItemDefinitionGroup>
+ <ClCompile>
+ <AdditionalIncludeDirectories>..\..\include\glib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ </ClCompile>
+ </ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="src\jabber.cpp" />
<ClCompile Include="src\jabber_adhoc.cpp" />
@@ -75,6 +80,7 @@
<ClCompile Include="src\jabber_userinfo.cpp" />
<ClCompile Include="src\jabber_util.cpp" />
<ClCompile Include="src\jabber_vcard.cpp" />
+ <ClCompile Include="src\jabber_voip.cpp" />
<ClCompile Include="src\jabber_xml.cpp" />
<ClCompile Include="src\jabber_xstatus.cpp" />
<ClCompile Include="src\jabber_zstream.cpp" />
@@ -107,7 +113,8 @@
</ItemGroup>
<ItemDefinitionGroup>
<Link>
- <AdditionalDependencies>libcrypto.lib;libssl.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <AdditionalDependencies>delayimp.lib;libcrypto.lib;libssl.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <DelayLoadDLLs>glib-2.0-0.dll;gobject-2.0-0.dll;gstreamer-1.0-0.dll;gstrtp-1.0-0.dll;gstsdp-1.0-0.dll;gstwebrtc-1.0-0.dll;%(DelayLoadDLLs)</DelayLoadDLLs>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
diff --git a/protocols/JabberG/jabber.vcxproj.filters b/protocols/JabberG/jabber.vcxproj.filters
index 30d426c7d2..7894068683 100644
--- a/protocols/JabberG/jabber.vcxproj.filters
+++ b/protocols/JabberG/jabber.vcxproj.filters
@@ -161,6 +161,9 @@
<ClCompile Include="src\jabber_mam.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="src\jabber_voip.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="src\jabber_byte.h">
diff --git a/protocols/JabberG/src/jabber_caps.cpp b/protocols/JabberG/src/jabber_caps.cpp
index 4b6fada073..e49f93d8a8 100644
--- a/protocols/JabberG/src/jabber_caps.cpp
+++ b/protocols/JabberG/src/jabber_caps.cpp
@@ -78,7 +78,11 @@ const JabberFeatCapPair g_JabberFeatCapPairs[] =
{ JABBER_FEAT_ROSTER_EXCHANGE, JABBER_CAPS_ROSTER_EXCHANGE, LPGEN("Supports Roster Exchange") },
{ JABBER_FEAT_DIRECT_MUC_INVITE, JABBER_CAPS_DIRECT_MUC_INVITE, LPGEN("Supports direct chat invitations (XEP-0249)") },
{ JABBER_FEAT_OMEMO_DEVICELIST_NOTIFY, JABBER_CAPS_OMEMO_DEVICELIST_NOTIFY, LPGEN("Receives information about OMEMO devices") },
- { JABBER_FEAT_CARBONS, JABBER_CAPS_CARBONS, LPGEN("Supports message carbons (XEP-0280)")},
+ { JABBER_FEAT_CARBONS, JABBER_CAPS_CARBONS, LPGEN("Supports message carbons (XEP-0280)")},
+ { JABBER_FEAT_JINGLE_ICEUDP, JABBER_CAPS_JINGLE_ICEUDP, LPGEN("Jingle ICE-UDP Transport") },
+ { JABBER_FEAT_JINGLE_RTP, JABBER_CAPS_JINGLE_RTP, LPGEN("Jingle RTP") },
+ { JABBER_FEAT_JINGLE_DTLS, JABBER_CAPS_JINGLE_DTLS, LPGEN("Jingle DTLS") },
+ { JABBER_FEAT_JINGLE_RTPAUDIO, JABBER_CAPS_JINGLE_RTPAUDIO, LPGEN("Jingle RTP Audio") },
};
const int g_cJabberFeatCapPairs = _countof(g_JabberFeatCapPairs);
@@ -347,6 +351,8 @@ JabberCapsBits CJabberProto::GetOwnCaps(bool IncludeDynamic)
jcb |= JABBER_CAPS_OMEMO_DEVICELIST_NOTIFY;
if (!m_bMsgAck)
jcb &= ~(JABBER_CAPS_CHAT_MARKERS | JABBER_CAPS_MESSAGE_RECEIPTS);
+ if (m_bEnableVOIP)
+ jcb |= JABBER_CAPS_JINGLE | JABBER_CAPS_JINGLE_ICEUDP | JABBER_CAPS_JINGLE_RTP | JABBER_CAPS_JINGLE_DTLS | JABBER_CAPS_JINGLE_RTPAUDIO;
return jcb;
}
diff --git a/protocols/JabberG/src/jabber_caps.h b/protocols/JabberG/src/jabber_caps.h
index 76313d77aa..fc37993fb0 100644
--- a/protocols/JabberG/src/jabber_caps.h
+++ b/protocols/JabberG/src/jabber_caps.h
@@ -195,6 +195,15 @@ typedef unsigned __int64 JabberCapsBits;
#define JABBER_FEAT_BITS "urn:xmpp:bob"
#define JABBER_CAPS_BITS ((JabberCapsBits)1<<50)
+#define JABBER_FEAT_JINGLE_ICEUDP "urn:xmpp:jingle:transports:ice-udp:1"
+#define JABBER_CAPS_JINGLE_ICEUDP ((JabberCapsBits)1<<51)
+#define JABBER_FEAT_JINGLE_RTP "urn:xmpp:jingle:apps:rtp:1"
+#define JABBER_CAPS_JINGLE_RTP ((JabberCapsBits)1<<52)
+#define JABBER_FEAT_JINGLE_DTLS "urn:xmpp:jingle:apps:dtls:0"
+#define JABBER_CAPS_JINGLE_DTLS ((JabberCapsBits)1<<53)
+#define JABBER_FEAT_JINGLE_RTPAUDIO "urn:xmpp:jingle:apps:rtp:audio"
+#define JABBER_CAPS_JINGLE_RTPAUDIO ((JabberCapsBits)1<<54)
+
#define JABBER_FEAT_ARCHIVE "urn:xmpp:archive"
#define JABBER_FEAT_BIND "urn:ietf:params:xml:ns:xmpp-bind"
#define JABBER_FEAT_CAPTCHA "urn:xmpp:captcha"
diff --git a/protocols/JabberG/src/jabber_menu.cpp b/protocols/JabberG/src/jabber_menu.cpp
index f37afe8eb4..808302b164 100644
--- a/protocols/JabberG/src/jabber_menu.cpp
+++ b/protocols/JabberG/src/jabber_menu.cpp
@@ -126,6 +126,13 @@ static int JabberPrebuildContactMenu(WPARAM hContact, LPARAM lParam)
return(ppro) ? ppro->OnPrebuildContactMenu(hContact, lParam) : 0;
}
+static INT_PTR JabberVOIPCallIinitiate(WPARAM hContact, LPARAM)
+{
+ CJabberProto *ppro = CMPlugin::getInstance(hContact);
+ ppro->VOIPCallIinitiate(hContact);
+ return 0;
+}
+
void g_MenuInit(void)
{
hStatusMenuInit = CreateHookableEvent(ME_JABBER_MENUINIT);
@@ -245,6 +252,16 @@ void g_MenuInit(void)
mi.hIcolibItem = g_plugin.getIconHandle(IDI_NODE_SERVER);
g_hMenuResourcesServer = Menu_AddContactMenuItem(&mi);
CreateServiceFunctionParam(mi.pszService, JabberMenuHandleResource, MENUITEM_SERVER);
+
+ SET_UID(mi, 0x2fe60fc5, 0x6417, 0x4f37, 0xa0, 0xbe, 0xa0, 0x59, 0x94, 0x11, 0x1d, 0xd9);
+ mi.root = nullptr;
+ mi.flags = 0;
+ mi.hIcolibItem = g_plugin.getIconHandle(IDI_NODE_RSS);
+ mi.name.a = LPGEN("Voice call");
+ mi.pszService = "Jabber/VOIPCallIinitiate";
+ mi.position = -1999902010;
+ Menu_AddContactMenuItem(&mi);
+ CreateServiceFunction(mi.pszService, JabberVOIPCallIinitiate);
}
void g_MenuUninit(void)
diff --git a/protocols/JabberG/src/jabber_opt.cpp b/protocols/JabberG/src/jabber_opt.cpp
index 00313c3d5d..c23c101555 100644
--- a/protocols/JabberG/src/jabber_opt.cpp
+++ b/protocols/JabberG/src/jabber_opt.cpp
@@ -706,8 +706,9 @@ public:
m_options.AddOption(LPGENW("Messaging"), LPGENW("Enable server-side history (XEP-0136)"), m_proto->m_bEnableMsgArchive);
m_options.AddOption(LPGENW("Messaging"), LPGENW("Enable Message Archive Management (XEP-0313)"), m_proto->m_bEnableMam);
m_options.AddOption(LPGENW("Messaging"), LPGENW("Enable carbon copies (XEP-0280)"), m_proto->m_bEnableCarbons);
- m_options.AddOption(LPGENW("Messaging"), LPGENW("Use Stream Management (XEP-0198) if possible (Testing)"), m_proto->m_bEnableStreamMgmt);
+ m_options.AddOption(LPGENW("Messaging"), LPGENW("Enable VOIP(for developers)"), m_proto->m_bEnableVOIP);
+ m_options.AddOption(LPGENW("Server options"), LPGENW("Use Stream Management (XEP-0198) if possible (Testing)"), m_proto->m_bEnableStreamMgmt);
m_options.AddOption(LPGENW("Server options"), LPGENW("Disable SASL authentication (for old servers)"), m_proto->m_bDisable3920auth);
m_options.AddOption(LPGENW("Server options"), LPGENW("Enable stream compression (if possible)"), m_proto->m_bEnableZlib);
diff --git a/protocols/JabberG/src/jabber_proto.cpp b/protocols/JabberG/src/jabber_proto.cpp
index 946ae533f4..cdae4a4df3 100644
--- a/protocols/JabberG/src/jabber_proto.cpp
+++ b/protocols/JabberG/src/jabber_proto.cpp
@@ -101,6 +101,7 @@ CJabberProto::CJabberProto(const char *aProtoName, const wchar_t *aUserName) :
m_bEnableUserActivity(this, "EnableUserActivity", true),
m_bEnableUserMood(this, "EnableUserMood", true),
m_bEnableUserTune(this, "EnableUserTune", false),
+ m_bEnableVOIP(this, "EnableVOIP", false),
m_bEnableZlib(this, "EnableZlib", true),
m_bFixIncorrectTimestamps(this, "FixIncorrectTimestamps", true),
m_bGcLogAffiliations(this, "GcLogAffiliations", false),
diff --git a/protocols/JabberG/src/jabber_proto.h b/protocols/JabberG/src/jabber_proto.h
index ce8a4c0881..e13cd79e3e 100644
--- a/protocols/JabberG/src/jabber_proto.h
+++ b/protocols/JabberG/src/jabber_proto.h
@@ -203,6 +203,7 @@ struct CJabberProto : public PROTO<CJabberProto>, public IJabberInterface
CMOption<bool> m_bEnableUserActivity;
CMOption<bool> m_bEnableUserMood;
CMOption<bool> m_bEnableUserTune;
+ CMOption<bool> m_bEnableVOIP;
CMOption<bool> m_bEnableZlib;
CMOption<bool> m_bFixIncorrectTimestamps;
CMOption<bool> m_bGcLogAffiliations;
@@ -886,6 +887,21 @@ struct CJabberProto : public PROTO<CJabberProto>, public IJabberInterface
void SetServerVcard(BOOL bPhotoChanged, wchar_t* szPhotoFileName);
void SaveVcardToDB(HWND hwndPage, int iPage);
+ //---- jabber_voip.c -----------------------------------------------------------------
+
+ bool OnICECandidate(const TiXmlElement *Node, const char *from);
+ bool OnRTPDescription(const TiXmlElement *Node);
+ bool VOIPCreatePipeline();
+ bool VOIPTerminateSession();
+ bool VOIPCallAccept(const TiXmlElement *jingleNode, const char *from);
+ bool VOIPCallIinitiate(MCONTACT hContact);
+
+ CMStringA m_voipSession, m_voipPeerJid;
+ CMStringA m_voipICEPwd, m_voipICEUfrag, m_medianame;
+ bool m_isOutgoing;
+ struct _GstElement *m_pipe1 = NULL;
+ struct _GstElement *m_webrtc1 = NULL;
+
//---- jabber_xml.c ------------------------------------------------------------------
void OnConsoleProcessXml(const TiXmlElement *node, uint32_t flags);
diff --git a/protocols/JabberG/src/jabber_thread.cpp b/protocols/JabberG/src/jabber_thread.cpp
index cedb5ed81b..ce3c613013 100644
--- a/protocols/JabberG/src/jabber_thread.cpp
+++ b/protocols/JabberG/src/jabber_thread.cpp
@@ -1770,41 +1770,78 @@ bool CJabberProto::OnProcessJingle(const TiXmlElement *node)
if (child) {
if ((type = XmlGetAttr(node, "type")) == nullptr)
return false;
+ const char *szAction = XmlGetAttr(child, "action");
+ const char *szSid = XmlGetAttr(child, "sid");
if ((!mir_strcmp(type, "get") || !mir_strcmp(type, "set"))) {
- const char *szAction = XmlGetAttr(child, "action");
const char *idStr = XmlGetAttr(node, "id");
const char *from = XmlGetAttr(node, "from");
- if (szAction && !mir_strcmp(szAction, "session-initiate")) {
- // if this is a Jingle 'session-initiate' and noone processed it yet, reply with "unsupported-applications"
- m_ThreadInfo->send(XmlNodeIq("result", idStr, from));
-
- XmlNodeIq iq("set", SerialNext(), from);
- TiXmlElement *jingleNode = iq << XCHILDNS("jingle", JABBER_FEAT_JINGLE);
-
- jingleNode << XATTR("action", "session-terminate");
- const char *szInitiator = XmlGetAttr(child, "initiator");
- if (szInitiator)
- jingleNode << XATTR("initiator", szInitiator);
- const char *szSid = XmlGetAttr(child, "sid");
- if (szSid)
- jingleNode << XATTR("sid", szSid);
-
- jingleNode << XCHILD("reason")
- << XCHILD("unsupported-applications");
- m_ThreadInfo->send(iq);
- return true;
- }
- else {
- // if it's something else than 'session-initiate' and noone processed it yet, reply with "unknown-session"
- XmlNodeIq iq("error", idStr, from);
- TiXmlElement *errNode = iq << XCHILD("error");
- errNode << XATTR("type", "cancel");
- errNode << XCHILDNS("item-not-found", "urn:ietf:params:xml:ns:xmpp-stanzas");
- errNode << XCHILDNS("unknown-session", "urn:xmpp:jingle:errors:1");
- m_ThreadInfo->send(iq);
- return true;
+ const char *szInitiator = XmlGetAttr(child, "initiator");
+ auto *content = XmlGetChildByTag(child, "content", "creator", "initiator");
+
+ if (szAction && szSid) {
+ if (!mir_strcmp(szAction, "session-initiate")) {
+ // if this is a Jingle 'session-initiate' and noone processed it yet, reply with "unsupported-applications"
+ m_ThreadInfo->send(XmlNodeIq("result", idStr, from));
+
+ const TiXmlElement *descr = XmlGetChildByTag(content, "description", "xmlns", JABBER_FEAT_JINGLE_RTP);
+ if (m_bEnableVOIP && m_voipSession == "" && descr) {
+ if (VOIPCallAccept(child, from)) {
+ m_voipSession = szSid;
+ m_voipPeerJid = from;
+ return true;
+ }
+ }
+
+ XmlNodeIq iq("set", SerialNext(), from);
+ TiXmlElement *jingleNode = iq << XCHILDNS("jingle", JABBER_FEAT_JINGLE);
+ jingleNode << XATTR("action", "session-terminate");
+ if (szInitiator)
+ jingleNode << XATTR("initiator", szInitiator);
+ if (szSid)
+ jingleNode << XATTR("sid", szSid);
+ jingleNode << XCHILD("reason") << XCHILD("unsupported-applications");
+
+ m_ThreadInfo->send(iq);
+ return true;
+ }
+ else if (!mir_strcmp(szAction, "session-accept")) {
+ if (m_bEnableVOIP && m_voipSession == szSid) {
+ m_ThreadInfo->send(XmlNodeIq("result", idStr, from));
+ OnRTPDescription(child);
+ return true;
+ }
+ }
+ else if (!mir_strcmp(szAction, "session-terminate")) {
+ if (m_bEnableVOIP && m_voipSession == szSid) {
+ // EndCall()
+ m_ThreadInfo->send(XmlNodeIq("result", idStr, from));
+ VOIPTerminateSession();
+ m_voipSession.Empty();
+ m_voipPeerJid.Empty();
+ return true;
+ }
+ }
+ else if (!mir_strcmp(szAction, "transport-info")) {
+ auto *transport = XmlGetChildByTag(content, "transport", "xmlns", JABBER_FEAT_JINGLE_ICEUDP);
+ if (m_bEnableVOIP && m_voipSession == szSid && transport) {
+ m_ThreadInfo->send(XmlNodeIq("result", idStr, from));
+ if (const TiXmlElement *candidate = XmlFirstChild(transport, "candidate")) {
+ OnICECandidate(candidate, from);
+ return true;
+ }
+ }
+ }
}
+
+ // if it's something else than 'session-initiate' and noone processed it yet, reply with "unknown-session"
+ XmlNodeIq iq("error", idStr, from);
+ TiXmlElement *errNode = iq << XCHILD("error");
+ errNode << XATTR("type", "cancel");
+ errNode << XCHILDNS("item-not-found", "urn:ietf:params:xml:ns:xmpp-stanzas");
+ errNode << XCHILDNS("unknown-session", "urn:xmpp:jingle:errors:1");
+ m_ThreadInfo->send(iq);
+ return true;
}
}
return false;
diff --git a/protocols/JabberG/src/jabber_voip.cpp b/protocols/JabberG/src/jabber_voip.cpp
new file mode 100644
index 0000000000..2f45d61350
--- /dev/null
+++ b/protocols/JabberG/src/jabber_voip.cpp
@@ -0,0 +1,519 @@
+#include "stdafx.h"
+
+#include <gst/gst.h>
+#include <gst/sdp/sdp.h>
+#include <gst/rtp/rtp.h>
+
+#define GST_USE_UNSTABLE_API
+#include <gst/webrtc/webrtc.h>
+
+#pragma comment(lib, "glib-2.0.lib")
+#pragma comment(lib, "gobject-2.0.lib")
+#pragma comment(lib, "gstreamer-1.0.lib")
+#pragma comment(lib, "gstrtp-1.0.lib")
+#pragma comment(lib, "gstsdp-1.0.lib")
+#pragma comment(lib, "gstwebrtc-1.0.lib")
+
+static std::list<CMStringA> remotecands;
+
+bool GetCandidateProp(char *output, byte maxlen, const char *candidate, const char *prop)
+{
+ const char *pprop = strstr(candidate, prop);
+ if (!pprop)
+ return false;
+
+ const char *val = pprop + strlen(prop);
+ while (*val == ' ') val++;
+ int i = 0;
+ while (*val != 0 && *val != ' ' && i < maxlen - 1)
+ output[i++] = *val++;
+ output[i] = 0;
+
+ return i > 0;
+}
+
+static void handle_media_stream(GstPad *pad, GstElement *pipe, const char *convert_name, const char *sink_name)
+{
+ GstPad *qpad;
+ GstElement *q, *conv, *resample, *sink;
+ GstPadLinkReturn ret;
+
+ gst_print("Trying to handle stream with %s ! %s", convert_name, sink_name);
+
+ q = gst_element_factory_make("queue", NULL);
+ g_assert_nonnull(q);
+ conv = gst_element_factory_make(convert_name, NULL);
+ g_assert_nonnull(conv);
+ sink = gst_element_factory_make(sink_name, NULL);
+ g_assert_nonnull(sink);
+
+ if (g_strcmp0(convert_name, "audioconvert") == 0) {
+ /* Might also need to resample, so add it just in case.
+ * Will be a no-op if it's not required. */
+ resample = gst_element_factory_make("audioresample", NULL);
+ g_assert_nonnull(resample);
+ gst_bin_add_many(GST_BIN(pipe), q, conv, resample, sink, NULL);
+ gst_element_sync_state_with_parent(q);
+ gst_element_sync_state_with_parent(conv);
+ gst_element_sync_state_with_parent(resample);
+ gst_element_sync_state_with_parent(sink);
+ gst_element_link_many(q, conv, resample, sink, NULL);
+ }
+ else {
+ gst_bin_add_many(GST_BIN(pipe), q, conv, sink, NULL);
+ gst_element_sync_state_with_parent(q);
+ gst_element_sync_state_with_parent(conv);
+ gst_element_sync_state_with_parent(sink);
+ gst_element_link_many(q, conv, sink, NULL);
+ }
+
+ qpad = gst_element_get_static_pad(q, "sink");
+
+ ret = gst_pad_link(pad, qpad);
+ g_assert_cmphex(ret, == , GST_PAD_LINK_OK);
+}
+
+static void on_incoming_decodebin_stream(GstElement * /*decodebin*/, GstPad *pad, GstElement *pipe)
+{
+ GstCaps *caps;
+ const gchar *name;
+
+ if (!gst_pad_has_current_caps(pad)) {
+ gst_printerr("Pad '%s' has no caps, can't do anything, ignoring\n", GST_PAD_NAME(pad));
+ return;
+ }
+
+ caps = gst_pad_get_current_caps(pad);
+ name = gst_structure_get_name(gst_caps_get_structure(caps, 0));
+
+ if (g_str_has_prefix(name, "video")) {
+ handle_media_stream(pad, pipe, "videoconvert", "autovideosink");
+ }
+ else if (g_str_has_prefix(name, "audio")) {
+ handle_media_stream(pad, pipe, "audioconvert", "autoaudiosink");
+ }
+ else {
+ gst_printerr("Unknown pad %s, ignoring", GST_PAD_NAME(pad));
+ }
+}
+
+static void on_incoming_stream_cb(GstElement */*webrtc*/, GstPad *pad, GstElement *pipe)
+{
+ GstElement *decodebin;
+ GstPad *sinkpad;
+
+ if (GST_PAD_DIRECTION(pad) != GST_PAD_SRC)
+ return;
+
+ decodebin = gst_element_factory_make("decodebin", NULL);
+ g_signal_connect(decodebin, "pad-added", G_CALLBACK(on_incoming_decodebin_stream), pipe);
+ gst_bin_add(GST_BIN(pipe), decodebin);
+ gst_element_sync_state_with_parent(decodebin);
+
+ sinkpad = gst_element_get_static_pad(decodebin, "sink");
+ gst_pad_link(pad, sinkpad);
+ gst_object_unref(sinkpad);
+}
+
+void on_offer_created_cb(GstPromise *promise, gpointer user_data)
+{
+ GstWebRTCSessionDescription *offer = NULL;
+ CJabberProto *jproto = (CJabberProto *)user_data;
+
+ GstStructure const *reply = gst_promise_get_reply(promise);
+ gst_structure_get(reply, jproto->m_isOutgoing ? "offer" : "answer", GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &offer, NULL);
+ gst_promise_unref(promise);
+ if (!offer) {
+ gst_print("Cannot process sdp");
+ return;
+ }
+
+ GstPromise *local_desc_promise = gst_promise_new();
+ g_signal_emit_by_name(jproto->m_webrtc1, "set-local-description", offer, local_desc_promise);
+ gst_promise_interrupt(local_desc_promise);
+ gst_promise_unref(local_desc_promise);
+
+ gchar *sdp_string = gst_sdp_message_as_text(offer->sdp);
+ gst_print("VOIP - Wanna send SDP offer:\r\n%s\r\n", sdp_string);
+ g_free(sdp_string);
+
+ const GstSDPMedia *media_audio = NULL;
+ for (unsigned int i = 0; i < gst_sdp_message_medias_len(offer->sdp); i++) {
+ const GstSDPMedia *m = gst_sdp_message_get_media(offer->sdp, i);
+ if (!strcmp(m->media, "audio"))
+ media_audio = m;
+ }
+ if (!media_audio) {
+ gst_print("No audio media in SDP");
+ return;
+ }
+
+ jproto->m_voipICEPwd = gst_sdp_media_get_attribute_val(media_audio, "ice-pwd");
+ jproto->m_voipICEUfrag = gst_sdp_media_get_attribute_val(media_audio, "ice-ufrag");
+ jproto->m_medianame = gst_sdp_media_get_attribute_val(media_audio, "mid");
+
+ //send it all
+ bool outgoing = jproto->m_isOutgoing;
+ XmlNodeIq iq("set", jproto->SerialNext(), jproto->m_voipPeerJid);
+ TiXmlElement *rjNode = iq << XCHILDNS("jingle", JABBER_FEAT_JINGLE);
+ rjNode << XATTR("sid", jproto->m_voipSession)
+ << XATTR("action", outgoing ? "session-initiate" : "session-accept")
+ << XATTR("initiator", outgoing ? jproto->m_ThreadInfo->fullJID : jproto->m_voipPeerJid);
+ if (!outgoing)
+ rjNode << XATTR("responder", jproto->m_ThreadInfo->fullJID);
+
+ TiXmlElement *content = rjNode << XCHILD("content") << XATTR("creator", "initiator") << XATTR("name", jproto->m_medianame);
+ TiXmlElement *description = content << XCHILDNS("description", JABBER_FEAT_JINGLE_RTP) << XATTR("media", "audio");
+
+ auto *opuspayload = description << XCHILD("payload-type") << XATTR("id", "111") << XATTR("name", "opus") << XATTR("clockrate", "48000") << XATTR("channels", "2");
+
+ opuspayload << XCHILD("parameter") << XATTR("name", "minptime") << XATTR("value", "10");
+ opuspayload << XCHILD("parameter") << XATTR("name", "useinbandfec") << XATTR("value", "1");
+ opuspayload << XCHILDNS("rtcp-fb", "urn:xmpp:jingle:apps:rtp:rtcp-fb:0") << XATTR("type", "transport-cc");
+
+ description << XCHILDNS("rtp-hdrext", "urn:xmpp:jingle:apps:rtp:rtp-hdrext:0") << XATTR("id", "1") << XATTR("uri", "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01");
+ /*
+ auto* source = description << XCHILDNS("source", "urn:xmpp:jingle:apps:rtp:ssma:0") << XATTR("ssrc", "2165039095");
+ source << XCHILD("parameter") << XATTR("name", "cname") << XATTR("value", "8ee+PcGu8BNwq22f");
+ source << XCHILD("parameter") << XATTR("name", "msid") << XATTR("value", "my-media-stream2 my-audio-track2");
+ source << XCHILD("parameter") << XATTR("name", "mslabel") << XATTR("value", "my-media-stream2");
+ source << XCHILD("parameter") << XATTR("name", "label") << XATTR("value", "my-audio-track2");*/
+
+ description << XCHILD("rtcp-mux");
+
+ //fingerprint
+ char hash[100];
+ if (sscanf(gst_sdp_media_get_attribute_val(media_audio, "fingerprint"), "sha-256 %95s", hash) == 1) {
+ auto *transport = content << XCHILDNS("transport", JABBER_FEAT_JINGLE_ICEUDP);
+ transport << XATTR("pwd", jproto->m_voipICEPwd) << XATTR("ufrag", jproto->m_voipICEUfrag);
+
+ auto *fingerprint = transport << XCHILD("fingerprint", hash);
+ fingerprint << XATTR("xmlns", JABBER_FEAT_JINGLE_DTLS) << XATTR("hash", "sha-256")
+ << XATTR("setup", gst_sdp_media_get_attribute_val(media_audio, "setup"));
+ }
+
+ jproto->m_ThreadInfo->send(iq);
+
+ gst_webrtc_session_description_free(offer);
+}
+
+void on_negotiation_needed_cb(GstElement *webrtcbin, gpointer user_data)
+{
+ if (((CJabberProto *)user_data)->m_isOutgoing) {
+ gst_print("Creating negotiation offer\n");
+
+ GstPromise *promise = gst_promise_new_with_change_func(on_offer_created_cb, user_data, NULL);
+ g_signal_emit_by_name(G_OBJECT(webrtcbin), "create-offer", NULL, promise);
+ }
+}
+
+static void on_offer_set(GstPromise *promise, gpointer user_data)
+{
+ gst_promise_unref(promise);
+ promise = gst_promise_new_with_change_func(on_offer_created_cb, user_data, NULL);
+ g_signal_emit_by_name(((CJabberProto *)user_data)->m_webrtc1, "create-answer", NULL, promise);
+}
+
+void send_ice_candidate_message_cb(G_GNUC_UNUSED GstElement */*webrtcbin*/, guint mline_index, gchar *candidate, CJabberProto *jproto)
+{
+ // parse candidate and send
+ char foundation[11], component[11], protocol[4], priority[11], ip[40], port[6], type[6];
+ int ret = sscanf(candidate, "candidate:%10s %10s %3s %10s %39s %5s typ %5s",
+ foundation, component, protocol, priority, ip, port, type);
+ if (ret != 7 || strcmp(protocol, "UDP"))
+ return;
+
+ gst_print("VOIP - Wanna send ice candidate(m-line_index=%d):\r\n%s\r\n", mline_index, candidate);
+ for (char *p = protocol; *p; ++p) *p = tolower(*p);
+
+ XmlNodeIq iq("set", jproto->SerialNext(), jproto->m_voipPeerJid);
+ TiXmlElement *rjNode = iq << XCHILDNS("jingle", JABBER_FEAT_JINGLE);
+ rjNode << XATTR("action", "transport-info") << XATTR("sid", jproto->m_voipSession);
+
+ TiXmlElement *content = rjNode << XCHILD("content");
+ content << XATTR("creator", "initiator") << XATTR("name", jproto->m_medianame);
+
+ auto *transport = content << XCHILDNS("transport", JABBER_FEAT_JINGLE_ICEUDP);
+ transport << XATTR("pwd", jproto->m_voipICEPwd) << XATTR("ufrag", jproto->m_voipICEUfrag);
+
+ auto *candidateNode = transport << XCHILD("candidate");
+ candidateNode << XATTR("type", type) << XATTR("protocol", protocol) << XATTR("ip", ip)
+ << XATTR("port", port) << XATTR("priority", priority) << XATTR("foundation", foundation) << XATTR("component", component);
+
+ char attr[255];
+ if (GetCandidateProp(attr, 255, candidate, "raddr"))
+ candidateNode << XATTR("rel-addr", attr);
+ if (GetCandidateProp(attr, 255, candidate, "rport"))
+ candidateNode << XATTR("rel-port", attr);
+
+ jproto->m_ThreadInfo->send(iq);
+}
+
+static gboolean check_plugins(void)
+{
+ const gchar *needed[] = { "opus", "nice", "webrtc", "dtls", "srtp", "rtpmanager",
+ /*"vpx", "videotestsrc", "audiotestsrc",*/ NULL };
+
+ GstRegistry *registry = gst_registry_get();
+ gst_registry_scan_path(registry, "libs\\gst_plugins");
+ gboolean ret = TRUE;
+ for (int i = 0; needed[i]; i++) {
+ GstPlugin *plugin = gst_registry_find_plugin(registry, needed[i]);
+ if (!plugin) {
+ gst_print("Required gstreamer plugin '%s' not found\n", needed[i]);
+ ret = FALSE;
+ }
+ else gst_object_unref(plugin);
+ }
+
+ return ret;
+}
+
+void dbgprint(const gchar *string)
+{
+ OutputDebugStringA(string);
+}
+
+bool CJabberProto::VOIPCreatePipeline(void)
+{
+ //gstreamer init
+ static bool gstinited = 0;
+ if (!gstinited) {
+ if (!LoadLibrary(L"gstreamer-1.0-0.dll")) {
+ MessageBoxA(0, "Cannot load Gstreamer library!", 0, MB_OK | MB_ICONERROR);
+ return false;
+ }
+ gst_init(NULL, NULL);
+ g_set_print_handler(dbgprint);
+ gst_print("preved medved");
+ if (!check_plugins()) {
+ MessageBoxA(0, "Gstreamer plugins not found!", 0, MB_OK | MB_ICONERROR);
+ return false;
+ }
+ gstinited = 1;
+ }
+
+ #define STUN_SERVER "stun-server=stun://stun.tng.de:3478 "
+ #define RTP_CAPS_OPUS "application/x-rtp,media=audio,encoding-name=OPUS,payload="
+ #define RTP_TWCC_URI "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01"
+
+ GError *error = NULL;
+ m_pipe1 = gst_parse_launch(
+ "webrtcbin bundle-policy=max-bundle name=sendrecv "
+ STUN_SERVER
+ "autoaudiosrc ! audioconvert ! audioresample ! queue ! opusenc ! rtpopuspay name=audiopay ! "
+ "queue ! " RTP_CAPS_OPUS "111 ! sendrecv. ", &error);
+
+ if (error) {
+ MessageBoxA(0, "Failed to parse launch: ", error->message, MB_OK);
+ g_error_free(error);
+ goto err;
+ }
+
+ m_webrtc1 = gst_bin_get_by_name(GST_BIN(m_pipe1), "sendrecv");
+ g_assert_nonnull(m_webrtc1);
+ if (!m_webrtc1)
+ MessageBoxA(0, "Epic fail", "cannot create m_webrtc1", MB_OK);
+
+ GstElement *audiopay = gst_bin_get_by_name(GST_BIN(m_pipe1), "audiopay");
+ g_assert_nonnull(audiopay);
+ GstRTPHeaderExtension *audio_twcc = gst_rtp_header_extension_create_from_uri(RTP_TWCC_URI);
+ g_assert_nonnull(audio_twcc);
+ gst_rtp_header_extension_set_id(audio_twcc, 1);
+ g_signal_emit_by_name(audiopay, "add-extension", audio_twcc);
+ g_clear_object(&audio_twcc);
+ g_clear_object(&audiopay);
+
+ // It will be called when the pipeline goes to PLAYING.
+ g_signal_connect(m_webrtc1, "on-negotiation-needed", G_CALLBACK(on_negotiation_needed_cb), this);
+ // It will be called when we obtain local ICE candidate
+ g_signal_connect(m_webrtc1, "on-ice-candidate", G_CALLBACK(send_ice_candidate_message_cb), this);
+ // idk
+ g_signal_connect(m_webrtc1, "pad-added", G_CALLBACK(on_incoming_stream_cb), m_pipe1);
+
+ // Lifetime is the same as the pipeline itself
+ gst_object_unref(m_webrtc1);
+
+ gst_print("Starting pipeline\n");
+ if (gst_element_set_state(GST_ELEMENT(m_pipe1), GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
+ goto err;
+
+ return true;
+
+err:
+ VOIPTerminateSession();
+ return FALSE;
+}
+
+bool CJabberProto::VOIPTerminateSession()
+{
+ gst_print("Terminating session");
+ if (m_pipe1) {
+ gst_element_set_state(GST_ELEMENT(m_pipe1), GST_STATE_NULL);
+ g_clear_object(&m_pipe1);
+ gst_print("Pipeline stopped\n");
+ gst_object_unref(m_pipe1);
+ }
+
+ m_voipICEPwd.Empty();
+ m_voipICEUfrag.Empty();
+ m_medianame.Empty();
+
+ m_voipSession.Empty();
+ m_voipPeerJid.Empty();
+ m_pipe1 = m_webrtc1 = NULL;
+ return true;
+}
+
+bool CJabberProto::OnRTPDescription(const TiXmlElement *jingleNode)
+{
+ // process remote offer
+ auto *content = XmlGetChildByTag(jingleNode, "content", "creator", "initiator");
+ auto *transport = XmlGetChildByTag(content, "transport", "xmlns", "urn:xmpp:jingle:transports:ice-udp:1");
+ auto *description = XmlGetChildByTag(content, "description", "xmlns", "urn:xmpp:jingle:apps:rtp:1");
+ auto *source = XmlGetChildByTag(description, "source", "xmlns", "urn:xmpp:jingle:apps:rtp:ssma:0");
+
+ CMStringA sdp_string(FORMAT, "v=0\r\no=- 0 0 IN IP4 0.0.0.0\r\ns=-\r\nt=0 0\r\na=ice-options:trickle\r\n"
+ "m=audio 9 UDP/TLS/RTP/SAVPF 111\r\nc=IN IP4 0.0.0.0\r\na=ice-ufrag:%s\r\na=ice-pwd:%s\r\na=rtcp-mux\r\na=sendrecv\r\na=rtpmap:111 OPUS/48000/2\r\n"
+
+ "a=rtcp-fb:111 transport-cc\r\na=fmtp:111 minptime=10;useinbandfec=1\r\n"
+ "a=ssrc:%s msid:%s\r\n"
+ "a=ssrc:%s cname:%s\r\n"
+
+ "a=mid:%s\r\na=setup:%s\r\na=fingerprint:sha-256 %s\r\na=rtcp-mux-only\r\n",
+ XmlGetAttr(transport, "ufrag"),
+ XmlGetAttr(transport, "pwd"),
+
+ XmlGetAttr(source, "ssrc"),
+ XmlGetAttr(XmlGetChildByTag(source, "parameter", "name", "msid"), "value"),
+ XmlGetAttr(source, "ssrc"),
+ XmlGetAttr(XmlGetChildByTag(source, "parameter", "name", "cname"), "value"),
+
+ XmlGetAttr(content, "name"),
+ XmlGetAttr(XmlFirstChild(transport, "fingerprint"), "setup"),
+ XmlFirstChild(transport, "fingerprint")->GetText());
+
+ GstSDPMessage *sdp;
+ int ret = gst_sdp_message_new(&sdp);
+ g_assert_cmphex(ret, == , GST_SDP_OK);
+ ret = gst_sdp_message_parse_buffer((guint8 *)sdp_string.c_str(), sdp_string.GetLength(), sdp);
+ if (ret != GST_SDP_OK) {
+ g_error("Could not parse SDP string\n");
+ return false;
+ }
+
+ gchar *str = gst_sdp_message_as_text(sdp);
+ gst_print("VOIP - Eating remote SDP offer:\r\n%s\r\n", str);
+ g_free(str);
+
+ if (m_isOutgoing) {
+ GstWebRTCSessionDescription *answer = gst_webrtc_session_description_new(GST_WEBRTC_SDP_TYPE_ANSWER, sdp);
+ g_assert_nonnull(answer);
+
+ GstPromise *promise = gst_promise_new();
+ g_signal_emit_by_name(m_webrtc1, "set-remote-description", answer, promise);
+ gst_promise_interrupt(promise);
+ gst_promise_unref(promise);
+ gst_webrtc_session_description_free(answer);
+ }
+ else {
+ // Set remote description on our pipeline
+ GstWebRTCSessionDescription *offer = gst_webrtc_session_description_new(GST_WEBRTC_SDP_TYPE_OFFER, sdp);
+ g_assert_nonnull(offer);
+
+ GstPromise *promise = gst_promise_new_with_change_func(on_offer_set, this, NULL);
+ g_signal_emit_by_name(m_webrtc1, "set-remote-description", offer, promise);
+ gst_webrtc_session_description_free(offer);
+ }
+
+ return true;
+}
+
+bool CJabberProto::OnICECandidate(const TiXmlElement *Node, const char *)
+{
+ CMStringA scandidate;
+ CMStringA proto(XmlGetAttr(Node, "protocol"));
+ proto.MakeUpper();
+
+ scandidate.AppendFormat("candidate:%s ", XmlGetAttr(Node, "foundation")); //FIXME
+ scandidate.AppendFormat("%s ", XmlGetAttr(Node, "component"));
+ scandidate.AppendFormat("%s ", proto.c_str());
+ scandidate.AppendFormat("%s ", XmlGetAttr(Node, "priority"));
+ scandidate.AppendFormat("%s ", XmlGetAttr(Node, "ip"));
+ scandidate.AppendFormat("%s ", XmlGetAttr(Node, "port"));
+ scandidate.AppendFormat("typ %s", XmlGetAttr(Node, "type"));
+
+ if (const char *tmp = XmlGetAttr(Node, "rel-addr"))
+ scandidate.AppendFormat(" raddr %s", tmp);
+ if (const char *tmp = XmlGetAttr(Node, "rel-port"))
+ scandidate.AppendFormat(" rport %s", tmp);
+ if (const char *generation = XmlGetAttr(Node, "generation"))
+ scandidate.AppendFormat(" generation %s", generation);
+
+ gst_print("VOIP - Accepting ICE candidate:\r\n%s\r\n", scandidate.c_str());
+ g_signal_emit_by_name(m_webrtc1, "add-ice-candidate", 0, scandidate.c_str());
+ return true;
+}
+
+bool CJabberProto::VOIPCallIinitiate(MCONTACT hContact)
+{
+ if (m_voipSession != "") {
+ VOIPTerminateSession();
+ MessageBoxA(0, "Terminated", NULL, 0);
+ return 0;
+ }
+
+ CMStringA jid(ptrA(getUStringA(hContact, "jid")));
+ if (jid == "")
+ return 0;
+ ptrA szResource(GetBestResourceName(jid));
+ if (szResource)
+ jid = MakeJid(jid, szResource);
+
+ CMStringA question(FORMAT, "Call %s?\r\n"
+ "It will disclose IP address to the peer and his server", jid.c_str());
+ if (MessageBoxA(0, question.c_str(), "Outgoing call", MB_YESNO | MB_ICONQUESTION) != IDYES)
+ return 0;
+
+ unsigned char tmp[16];
+ Utils_GetRandom(tmp, sizeof(tmp));
+
+ m_isOutgoing = true;
+ m_voipSession = ptrA(mir_base64_encode(tmp, sizeof(tmp)));
+ m_voipPeerJid = jid.c_str();
+
+ VOIPCreatePipeline();
+ return 0;
+}
+
+bool CJabberProto::VOIPCallAccept(const TiXmlElement *jingleNode, const char *from)
+{
+ if (!from || !jingleNode)
+ return false;
+
+ CMStringW question(FORMAT, TranslateT("Accept call from %s?\r\nIt will disclose IP address to the peer and his server"), from);
+ if (MessageBoxW(0, question, TranslateT("Incomig call"), MB_YESNO | MB_ICONQUESTION) != IDYES)
+ return false;
+
+ m_isOutgoing = false;
+
+ if (!VOIPCreatePipeline())
+ return false;
+
+ OnRTPDescription(jingleNode);
+
+ // ringing message
+ XmlNodeIq iq("set", SerialNext(), from);
+ TiXmlElement *rjNode = iq << XCHILDNS("jingle", JABBER_FEAT_JINGLE);
+ rjNode << XATTR("action", "session-info");
+ const char *szInitiator = XmlGetAttr(jingleNode, "initiator");
+ if (szInitiator)
+ rjNode << XATTR("initiator", szInitiator);
+ const char *szSid = XmlGetAttr(jingleNode, "sid");
+ if (szSid)
+ rjNode << XATTR("sid", szSid);
+ rjNode << XCHILDNS("ringing", "urn:xmpp:jingle:apps:rtp:info:1");
+
+ m_ThreadInfo->send(iq);
+ return true;
+}