Add new param for setting remote session description and ICE candidiate for WebRTC...
[platform/core/api/mediastreamer.git] / src / media_streamer_gst_webrtc.c
1 /*
2  * Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #ifndef GST_USE_UNSTABLE_API
18 #define GST_USE_UNSTABLE_API
19 #include <gst/webrtc/webrtc.h>
20 #endif
21 #include "media_streamer_util.h"
22 #include "media_streamer_priv.h"
23 #include "media_streamer_gst.h"
24 #include "media_streamer_gst_webrtc.h"
25 #include "media_streamer_node.h"
26
27 static gchar *__make_ice_candidate_message(guint mlineindex, gchar *candidate)
28 {
29         JsonObject *ice, *msg;
30         gchar *text;
31
32         ms_retvm_if(candidate == NULL, NULL, "candidate is NULL");
33
34         ice = json_object_new();
35         json_object_set_string_member(ice, "candidate", candidate);
36         json_object_set_int_member(ice, "sdpMLineIndex", mlineindex);
37
38         msg = json_object_new();
39         json_object_set_object_member(msg, "ice", ice);
40
41         text = ms_get_string_from_json_object(msg);
42
43         json_object_unref(msg);
44
45         return text;
46 }
47
48 static gchar* __make_sdp_message(GstWebRTCSessionDescription *desc)
49 {
50         gchar *text;
51         JsonObject *msg, *sdp;
52
53         text = gst_sdp_message_as_text(desc->sdp);
54         sdp = json_object_new();
55
56         if (desc->type == GST_WEBRTC_SDP_TYPE_OFFER) {
57                 ms_info("Making offer message:\n%s", text);
58                 json_object_set_string_member(sdp, "type", "offer");
59         } else if (desc->type == GST_WEBRTC_SDP_TYPE_ANSWER) {
60                 ms_info("Making answer message:\n%s", text);
61                 json_object_set_string_member(sdp, "type", "answer");
62         } else {
63                 ms_error("invalid description type");
64                 return NULL;
65         }
66
67         json_object_set_string_member(sdp, "sdp", text);
68         g_free(text);
69
70         msg = json_object_new();
71         json_object_set_object_member(msg, "sdp", sdp);
72
73         text = ms_get_string_from_json_object(msg);
74
75         json_object_unref(msg);
76
77         return text;
78 }
79
80 static void __trigger_message_callback(media_streamer_node_s *webrtc_node, gchar *message)
81 {
82         ms_retm_if(webrtc_node == NULL, "webrtc_node is NULL");
83
84         ms_debug("message is : \n%s", message);
85
86         if (webrtc_node->user_cb.callback) {
87                 ms_debug("=====> Now trigger user callback(%p)", webrtc_node->user_cb.callback);
88                 ((media_streamer_webrtc_message_cb)(webrtc_node->user_cb.callback))(webrtc_node, message, webrtc_node->user_cb.user_data);
89                 ms_debug("<===== End of the callback");
90         } else {
91                 ms_warning("message callback is NULL");
92         }
93 }
94
95 static void __ms_webrtcbin_set_session_description(GstElement *webrtcbin, GstWebRTCSessionDescription *session_description, gboolean is_remote)
96 {
97         GstPromise *promise;
98         ms_retm_if(session_description == NULL, "session_description is NULL");
99         ms_retm_if(webrtcbin == NULL, "webrtcbin is NULL");
100
101         ms_debug_fenter();
102
103         promise = gst_promise_new();
104         g_signal_emit_by_name(webrtcbin, is_remote? "set-remote-description" : "set-local-description", session_description, promise);
105         gst_promise_interrupt(promise);
106         gst_promise_unref(promise);
107
108         ms_debug_fleave();
109 }
110
111 static void __on_offer_created_cb(GstPromise *promise, gpointer user_data)
112 {
113         GstWebRTCSessionDescription *offer = NULL;
114         const GstStructure *reply;
115         media_streamer_node_s *webrtc_node = (media_streamer_node_s *)user_data;
116         node_info_s *node_klass_type = NULL;
117         GstElement *webrtcbin = NULL;
118         gchar *sdp_msg;
119
120         ms_retm_if(promise == NULL, "promise is NULL");
121         ms_retm_if(webrtc_node == NULL, "webrtc_node is NULL");
122         ms_retm_if(gst_promise_wait(promise) != GST_PROMISE_RESULT_REPLIED, "promise is not for replied result");
123
124         ms_debug_fenter();
125
126         node_klass_type = ms_node_get_klass_by_its_type(MEDIA_STREAMER_NODE_TYPE_WEBRTC);
127         if (!(webrtcbin = ms_find_element_in_bin_by_type(webrtc_node->gst_element, node_klass_type))) {
128                 ms_error("Could not find webrtcbin by type[%s, %s]", node_klass_type->klass_name, node_klass_type->default_name);
129                 return;
130         }
131
132         reply = gst_promise_get_reply(promise);
133         gst_structure_get(reply, "offer",
134                 GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &offer, NULL);
135         gst_promise_unref(promise);
136
137         __ms_webrtcbin_set_session_description(webrtcbin, offer, FALSE);
138
139         sdp_msg = __make_sdp_message(offer);
140         gst_webrtc_session_description_free(offer);
141
142         /* Send local description to peer */
143         __trigger_message_callback(webrtc_node, sdp_msg);
144         g_free(sdp_msg);
145
146         ms_debug_fleave();
147 }
148
149 static void __on_answer_created_cb(GstPromise * promise, gpointer user_data)
150 {
151         GstWebRTCSessionDescription *answer = NULL;
152         const GstStructure *reply;
153         media_streamer_node_s *node = (media_streamer_node_s *)user_data;
154         node_info_s *node_klass_type = NULL;
155         gchar *sdp_msg;
156         GstElement *webrtcbin;
157
158         ms_retm_if(promise == NULL, "promise is NULL");
159         ms_retm_if(node == NULL, "node is NULL");
160         ms_retm_if(node->gst_element == NULL, "webrtc_container is NULL");
161         ms_retm_if(gst_promise_wait(promise) != GST_PROMISE_RESULT_REPLIED, "promise is not for replied result");
162
163         ms_debug_fenter();
164
165         node_klass_type = ms_node_get_klass_by_its_type(MEDIA_STREAMER_NODE_TYPE_WEBRTC);
166         if (!(webrtcbin = ms_find_element_in_bin_by_type(node->gst_element, node_klass_type))) {
167                 ms_error("Could not find webrtcbin by type[%s, %s]", node_klass_type->klass_name, node_klass_type->default_name);
168                 return;
169         }
170
171         reply = gst_promise_get_reply(promise);
172         gst_structure_get(reply, "answer", GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &answer, NULL);
173         gst_promise_unref(promise);
174
175         __ms_webrtcbin_set_session_description(webrtcbin, answer, FALSE);
176
177         sdp_msg = __make_sdp_message(answer);
178         gst_webrtc_session_description_free(answer);
179
180         /* Send local description to peer */
181         __trigger_message_callback(node, sdp_msg);
182         g_free(sdp_msg);
183
184         ms_debug_fleave();
185 }
186
187 int ms_webrtcbin_set_remote_session_description(media_streamer_node_s *node, const char *sdp_msg)
188 {
189         GstSDPMessage *gst_sdp;
190         gchar *sdp;
191         gchar *type;
192         GstWebRTCSessionDescription *answer, *offer;
193         node_info_s *node_klass_type;
194         GstElement *webrtcbin;
195         int ret = MEDIA_STREAMER_ERROR_NONE;
196
197         ms_retvm_if(node == NULL || node->gst_element == NULL, MEDIA_STREAMER_ERROR_INVALID_PARAMETER, "Error: empty webrtcbin");
198         ms_retvm_if(sdp_msg == NULL, MEDIA_STREAMER_ERROR_INVALID_PARAMETER, "sdp_msg is NULL");
199
200         ms_debug_fenter();
201
202
203         node_klass_type = ms_node_get_klass_by_its_type(MEDIA_STREAMER_NODE_TYPE_WEBRTC);
204         if (!(webrtcbin = ms_find_element_in_bin_by_type(node->gst_element, node_klass_type))) {
205                 ms_error("Could not find webrtcbin by type[%s, %s]", node_klass_type->klass_name, node_klass_type->default_name);
206                 return MEDIA_STREAMER_ERROR_INVALID_OPERATION;
207         }
208
209         ret = ms_webrtc_get_sdp_from_message(sdp_msg, &sdp, &type);
210         if (ret != MEDIA_STREAMER_ERROR_NONE)
211                 goto end;
212
213         ret = gst_sdp_message_new(&gst_sdp);
214         if (ret != GST_SDP_OK) {
215                 ret = MEDIA_STREAMER_ERROR_INVALID_OPERATION;
216                 goto end;
217         }
218
219         ret = gst_sdp_message_parse_buffer((guint8 *)sdp, strlen(sdp), gst_sdp);
220         if (ret != GST_SDP_OK) {
221                 ret = MEDIA_STREAMER_ERROR_INVALID_OPERATION;
222                 goto end;
223         }
224
225         if (g_str_equal(type, "answer")) {
226                 answer = gst_webrtc_session_description_new(GST_WEBRTC_SDP_TYPE_ANSWER, gst_sdp);
227                 g_assert_nonnull(answer);
228
229                 __ms_webrtcbin_set_session_description(webrtcbin, answer, TRUE);
230                 gst_webrtc_session_description_free(answer);
231         } else if (g_str_equal(type, "offer")) {
232                 offer = gst_webrtc_session_description_new(GST_WEBRTC_SDP_TYPE_OFFER, gst_sdp);
233                 g_assert_nonnull(offer);
234
235                 __ms_webrtcbin_set_session_description(webrtcbin, offer, TRUE);
236                 gst_webrtc_session_description_free(offer);
237
238                 ms_webrtcbin_on_negotiation_process_answer(webrtcbin, node);
239         } else {
240                 ms_error("type is %s, it is not a answer or offer", type);
241         }
242
243 end:
244         MS_SAFE_GFREE(sdp);
245         MS_SAFE_GFREE(type);
246
247         ms_debug_fleave();
248
249         return ret;
250 }
251
252 int ms_webrtcbin_add_ice_candidate(media_streamer_node_s *node, const char *ice_msg)
253 {
254         gchar *candidate;
255         gint sdpmlineindex;
256         node_info_s *node_klass_type;
257         GstElement *webrtcbin;
258         int ret;
259
260         ms_retvm_if(node == NULL || node->gst_element == NULL, MEDIA_STREAMER_ERROR_INVALID_PARAMETER, "Error: empty webrtcbin");
261         ms_retvm_if(ice_msg == NULL, MEDIA_STREAMER_ERROR_INVALID_PARAMETER, "ice_msg is NULL");
262
263         ms_debug_fenter();
264
265         node_klass_type = ms_node_get_klass_by_its_type(MEDIA_STREAMER_NODE_TYPE_WEBRTC);
266         if (!(webrtcbin = ms_find_element_in_bin_by_type(node->gst_element, node_klass_type))) {
267                 ms_error("Could not find webrtcbin by type[%s, %s]", node_klass_type->klass_name, node_klass_type->default_name);
268                 return MEDIA_STREAMER_ERROR_INVALID_OPERATION;
269         }
270
271         ret = ms_webrtc_get_ice_candidate_from_message(ice_msg, &candidate, &sdpmlineindex);
272         if (ret != MEDIA_STREAMER_ERROR_NONE)
273                 return ret;
274
275         /*Add ice candidate sent by remote peer */
276         g_signal_emit_by_name(webrtcbin, "add-ice-candidate", sdpmlineindex, candidate);
277
278         g_free(candidate);
279
280         return MEDIA_STREAMER_ERROR_NONE;
281 }
282
283 void ms_webrtcbin_on_negotiation_process_answer(GstElement *webrtcbin, media_streamer_node_s *webrtc_node)
284 {
285         GstPromise *promise;
286
287         ms_retm_if(webrtcbin == NULL, "webrtcbin is NULL");
288
289         ms_debug_fenter();
290
291         promise = gst_promise_new_with_change_func(__on_answer_created_cb, webrtc_node, NULL);
292         g_signal_emit_by_name(G_OBJECT(webrtcbin), "create-answer", NULL, promise);
293
294         ms_debug_fleave();
295 }
296
297 void ms_webrtcbin_on_negotiation_needed_cb(GstElement *webrtcbin, gpointer user_data)
298 {
299         GstPromise *promise;
300
301         ms_retm_if(webrtcbin == NULL, "webrtcbin is NULL");
302         ms_retm_if(user_data == NULL, "user_data is NULL");
303
304         ms_debug_fenter();
305
306         promise = gst_promise_new_with_change_func(__on_offer_created_cb, user_data, NULL);
307         g_signal_emit_by_name(G_OBJECT(webrtcbin), "create-offer", NULL, promise);
308
309         ms_debug_fleave();
310 }
311
312 void ms_webrtcbin_on_ice_candidate_cb(GstElement *webrtcbin, guint mlineindex, gchar *candidate, gpointer user_data)
313 {
314         gchar *ice_candidate_msg = NULL;
315         media_streamer_node_s *webrtc_node = (media_streamer_node_s *)user_data;
316
317         ms_retm_if(webrtcbin == NULL, "webrtcbin is NULL");
318         ms_retm_if(candidate == NULL, "candidate is NULL");
319         ms_retm_if(webrtc_node == NULL, "webrtc_node is NULL");
320
321         ice_candidate_msg = __make_ice_candidate_message(mlineindex, candidate);
322
323         __trigger_message_callback(webrtc_node, ice_candidate_msg);
324
325         g_free(ice_candidate_msg);
326 }
327
328 void ms_webrtcbin_notify_ice_gathering_state_cb(GstElement *webrtcbin, GParamSpec * pspec, gpointer user_data)
329 {
330         GstWebRTCICEGatheringState ice_gather_state;
331         const gchar *new_state = "UNKNOWN";
332
333         g_object_get(webrtcbin, "ice-gathering-state", &ice_gather_state, NULL);
334
335         switch (ice_gather_state) {
336         case GST_WEBRTC_ICE_GATHERING_STATE_NEW:
337                 new_state = "NEW";
338                 break;
339         case GST_WEBRTC_ICE_GATHERING_STATE_GATHERING:
340                 new_state = "GATHERING";
341                 break;
342         case GST_WEBRTC_ICE_GATHERING_STATE_COMPLETE:
343                 new_state = "COMPLETE";
344                 break;
345         }
346
347         ms_info("ICE gathering state changed to [%s]", new_state);
348 }
349
350 static void __data_channel_on_error_cb(GObject *data_channel, gpointer user_data)
351 {
352         ms_retm_if(data_channel == NULL, "data_channel is NULL");
353
354         ms_debug_fenter();
355
356         ms_debug_fleave();
357 }
358
359 static void __data_channel_on_open_cb(GObject *data_channel, gpointer user_data)
360 {
361         GBytes *bytes = NULL;
362
363         ms_retm_if(data_channel == NULL, "data_channel is NULL");
364
365         ms_debug_fenter();
366
367         bytes = g_bytes_new("data", strlen("data"));
368
369         g_signal_emit_by_name(data_channel, "send-string", "Hi! from GStreamer");
370         g_signal_emit_by_name(data_channel, "send-data", bytes);
371
372         g_bytes_unref(bytes);
373
374         ms_debug_fleave();
375 }
376
377 static void __data_channel_on_close_cb(GObject *data_channel, gpointer user_data)
378 {
379         ms_retm_if(data_channel == NULL, "data_channel is NULL");
380
381         ms_debug_fenter();
382
383         ms_debug_fleave();
384 }
385
386 static void __data_channel_on_message_string_cb(GObject *data_channel, gchar *message, gpointer user_data)
387 {
388         ms_retm_if(data_channel == NULL, "data_channel is NULL");
389         ms_retm_if(message == NULL, "message is NULL");
390
391         ms_info("Received message: %s", message);
392 }
393
394 static void __connect_data_channel_signals(GObject *data_channel)
395 {
396         ms_retm_if(data_channel == NULL, "data_channel is NULL");
397
398         ms_debug_fenter();
399
400         g_signal_connect(data_channel, "on-error", G_CALLBACK(__data_channel_on_error_cb), NULL);
401         g_signal_connect(data_channel, "on-open", G_CALLBACK(__data_channel_on_open_cb), NULL);
402         g_signal_connect(data_channel, "on-close", G_CALLBACK(__data_channel_on_close_cb), NULL);
403         g_signal_connect(data_channel, "on-message-string", G_CALLBACK(__data_channel_on_message_string_cb), NULL);
404
405         ms_debug_fleave();
406 }
407
408 void ms_webrtcbin_on_data_channel_cb(GstElement *webrtcbin, GObject *data_channel, gpointer user_data)
409 {
410         media_streamer_s *ms_streamer = (media_streamer_s *)user_data;
411
412         ms_retm_if(ms_streamer == NULL, "ms_streamer is NULL");
413         ms_retm_if(data_channel == NULL, "data_channel is NULL");
414
415         ms_debug_fenter();
416
417         __connect_data_channel_signals(data_channel);
418
419         ms_debug_fleave();
420 }
421
422 void ms_webrtcbin_pad_added_cb(GstElement *webrtcbin, GstPad *new_pad, gpointer user_data)
423 {
424         media_streamer_s *ms_streamer = (media_streamer_s *)user_data;
425
426         ms_retm_if(new_pad == NULL, "new_pad is NULL");
427         ms_retm_if(ms_streamer == NULL, "ms_streamer is NULL");
428         ms_retm_if(GST_PAD_DIRECTION(new_pad) != GST_PAD_SRC, "new_pad is not for source");
429
430         ms_debug_fenter();
431
432         ms_debug("Pad [%s] added on [%s]", GST_PAD_NAME(new_pad), GST_ELEMENT_NAME(webrtcbin));
433
434         ms_debug_fleave();
435 }
436
437 GstElement *ms_webrtc_element_create(void)
438 {
439         GstElement *webrtc_container;
440         GstElement *webrtcbin;
441         GstGhostPad *ghost_pad_video_in;
442
443         ms_debug_fenter();
444
445         webrtc_container = gst_bin_new("webrtc_container");
446         ms_retvm_if(!webrtc_container, (GstElement *) NULL, "Error: creating elements for webrtc container");
447
448         ms_add_no_target_ghostpad(webrtc_container, MS_RTP_PAD_VIDEO_IN, GST_PAD_SINK);
449
450         MS_SET_INT_STATIC_STRING_PARAM(webrtc_container, MEDIA_STREAMER_PARAM_WEBRTC_PEER_TYPE, DEFAULT_WEBRTC_PEER);
451         MS_SET_INT_STATIC_STRING_PARAM(webrtc_container, MEDIA_STREAMER_PARAM_WEBRTC_REMOTE_SESSION_DESCRIPTION, NULL);
452         MS_SET_INT_STATIC_STRING_PARAM(webrtc_container, MEDIA_STREAMER_PARAM_WEBRTC_ADD_ICE_CANDIDATE, NULL);
453
454         if (!(webrtcbin = ms_element_create("webrtcbin", NULL))) {
455                 ms_error("Failed to create webrtcbin element");
456                 return NULL;
457         }
458
459         /* FIXME: these should be set from user */
460         g_object_set(G_OBJECT(webrtcbin), "bundle-policy", 3, NULL); // 3:max-bundle
461         g_object_set(G_OBJECT(webrtcbin), "stun-server", "stun://stun.l.google.com:19302", NULL);
462
463         ms_bin_add_element(webrtc_container, webrtcbin, FALSE);
464
465         if (!(ghost_pad_video_in = (GstGhostPad *)gst_element_get_static_pad(webrtc_container, MS_RTP_PAD_VIDEO_IN))) {
466                 ms_error("Failed to get ghost pad for webrtc_container");
467                 return NULL;
468         }
469
470         if (!(gst_ghost_pad_set_target(ghost_pad_video_in, gst_element_get_request_pad(webrtcbin, "sink_%u")))) {
471                 ms_info("Failed to gst_ghost_pad_set_target() for %s", MS_RTP_PAD_VIDEO_IN);
472                 /* release resources */
473                 return NULL;
474         }
475
476         ms_debug_fleave();
477
478         return webrtc_container;
479 }