Add webrtc_create_offer() and webrtc_create_answer() API 58/242858/14
authorSangchul Lee <sc11.lee@samsung.com>
Tue, 1 Sep 2020 09:32:31 +0000 (18:32 +0900)
committerSangchul Lee <sc11.lee@samsung.com>
Thu, 10 Sep 2020 01:16:11 +0000 (10:16 +0900)
It corresponds to the createOffer() and createAnswer() method
of the RTCPeerConnection respectively.

[Version] 0.1.9
[Issue Type] API

Change-Id: Ib2c3fc35a5b9adc7a2d35ad92f7001869a7d9dc4
Signed-off-by: Sangchul Lee <sc11.lee@samsung.com>
CMakeLists.txt
include/webrtc.h
include/webrtc_private.h
packaging/capi-media-webrtc.spec
src/webrtc.c
src/webrtc_private.c
test/webrtc_test.c

index 354f6a63b0fd3f6c4fa5b2a043c93e6ac80e4db8..6757b966d2fb6771f75e873603d1197859d32f59 100644 (file)
@@ -11,7 +11,7 @@ SET(LIB_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/lib")
 SET(INC_DIR include)
 INCLUDE_DIRECTORIES(${INC_DIR})
 
-SET(dependents "dlog glib-2.0 gstreamer-1.0)
+SET(dependents "dlog glib-2.0 gstreamer-1.0 gstreamer-webrtc-1.0 json-glib-1.0")
 SET(pc_dependents "capi-base-common" )
 
 INCLUDE(FindPkgConfig)
index b2867c0fb8b58ffe83822322804a891af724cd43..4a1de933495cb7d6d2ae15d82bddf7578c4ff449 100644 (file)
@@ -247,6 +247,39 @@ int webrtc_set_negotiation_needed_cb(webrtc_h webrtc, webrtc_negotiation_needed_
  */
 int webrtc_unset_negotiation_needed_cb(webrtc_h webrtc);
 
+/**
+ * @brief Creates SDP offer to start a new WebRTC connection to a remote peer.
+ * @since_tizen 6.0
+ * @remarks The @a offer should be released using free().
+ * @param [in] webrtc      WebRTC handle
+ * @param [out] offer      SDP offer
+ * @return @c 0 on success,
+ *         otherwise a negative error value
+ * @retval #WEBRTC_ERROR_NONE    Successful
+ * @retval #WEBRTC_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #WEBRTC_ERROR_INVALID_OPERATION Invalid operation
+ * @retval #WEBRTC_ERROR_INVALID_STATE Invalid state
+ * @pre @a webrtc state must be set to #WEBRTC_STATE_PLAYING.
+ * @see webrtc_negotiation_needed_cb()
+ */
+int webrtc_create_offer(webrtc_h webrtc, char **offer);
+
+/**
+ * @brief Creates SDP answer to an offer received from a remote peer during the negotiation of a WebRTC connection.
+ * @since_tizen 6.0
+ * @remarks The @a answer should be released using free().
+ * @param [in] webrtc      WebRTC handle
+ * @param [out] answer     SDP answer
+ * @return @c 0 on success,
+ *         otherwise a negative error value
+ * @retval #WEBRTC_ERROR_NONE    Successful
+ * @retval #WEBRTC_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #WEBRTC_ERROR_INVALID_OPERATION Invalid operation
+ * @retval #WEBRTC_ERROR_INVALID_STATE Invalid state
+ * @pre @a webrtc state must be set to #WEBRTC_STATE_PLAYING.
+ */
+int webrtc_create_answer(webrtc_h webrtc, char **answer);
+
 /**
  * @}
  */
index 1509645e489e598d1d217356cf899675209adccd..47bf9e611116f87aa2eb65ea50dc7882674dde21 100644 (file)
 #include <gst/gst.h>
 #include <stdio.h>
 #include <dlog.h>
+#ifndef GST_USE_UNSTABLE_API
+#define GST_USE_UNSTABLE_API
+#include <gst/webrtc/webrtc.h>
+#endif
 
 #ifdef __cplusplus
 extern "C" {
@@ -131,10 +135,14 @@ typedef struct _webrtc_s {
        webrtc_ini_s ini;
 
        GMutex mutex;
+       GMutex desc_mutex;
+       GCond desc_cond;
 
        webrtc_gst_s gst;
 
        gchar *stun_server_url;
+       gchar *desc_offer;
+       gchar *desc_answer;
 
        webrtc_state_e state;
        webrtc_state_e pend_state;
@@ -163,6 +171,9 @@ int _gst_pipeline_set_state(webrtc_s *webrtc, GstState state);
 int _add_media_source(webrtc_s *webrtc, webrtc_media_source_type_e type, unsigned int *source_id);
 int _remove_media_source(webrtc_s *webrtc, unsigned int source_id);
 
+int _webrtcbin_create_offer(webrtc_s *webrtc, char **offer);
+int _webrtcbin_create_answer(webrtc_s *webrtc, char **answer);
+
 #ifdef __cplusplus
 }
 #endif /* __cplusplus */
index 5c04bc7f19ef6e816e315ebb4cfd66304e04d041..0b5a3baa05eaa4a5fa85067ef1066a9206849356 100644 (file)
@@ -1,6 +1,6 @@
 Name:       capi-media-webrtc
 Summary:    A WebRTC library in Tizen Native API
-Version:    0.1.8
+Version:    0.1.9
 Release:    0
 Group:      Multimedia/API
 License:    Apache-2.0
@@ -12,8 +12,10 @@ BuildRequires:  pkgconfig(dlog)
 BuildRequires:  pkgconfig(glib-2.0)
 BuildRequires:  pkgconfig(capi-base-common)
 BuildRequires:  pkgconfig(gstreamer-1.0)
+BuildRequires:  pkgconfig(gstreamer-webrtc-1.0)
 BuildRequires:  pkgconfig(appcore-efl)
 BuildRequires:  pkgconfig(elementary)
+BuildRequires:  pkgconfig(json-glib-1.0)
 
 %description
 A WebRTC library in Tizen Native API.
index 335b06220fe27a7452c6707bab4e1641c3e32e88..cca116e1e4027d166d53a7fbb625d19c437dbb47 100644 (file)
@@ -33,6 +33,9 @@ int webrtc_create(webrtc_h *webrtc)
        g_mutex_init(&_webrtc->mutex);
        g_mutex_lock(&_webrtc->mutex);
 
+       g_mutex_init(&_webrtc->desc_mutex);
+       g_cond_init(&_webrtc->desc_cond);
+
        _ini_load(_webrtc);
        _gst_init(_webrtc);
        _gst_build_pipeline(_webrtc);
@@ -64,6 +67,9 @@ int webrtc_destroy(webrtc_h webrtc)
 
        _gst_destroy_pipeline(_webrtc);
 
+       g_mutex_clear(&_webrtc->desc_mutex);
+       g_cond_clear(&_webrtc->desc_cond);
+
        g_mutex_unlock(&_webrtc->mutex);
        g_mutex_clear(&_webrtc->mutex);
 
@@ -239,3 +245,46 @@ int webrtc_unset_negotiation_needed_cb(webrtc_h webrtc)
 
        return WEBRTC_ERROR_NONE;
 }
+
+int webrtc_create_offer(webrtc_h webrtc, char **offer)
+{
+       int ret = WEBRTC_ERROR_NONE;
+       webrtc_s *_webrtc = (webrtc_s*)webrtc;
+
+       RET_VAL_IF(_webrtc == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "webrtc is NULL");
+       RET_VAL_IF(offer == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "offer is NULL");
+
+       g_mutex_lock(&_webrtc->mutex);
+
+       RET_VAL_WITH_UNLOCK_IF(_webrtc->state != WEBRTC_STATE_PLAYING, WEBRTC_ERROR_INVALID_STATE, &_webrtc->mutex, "the state should be PLAYING");
+
+       LOG_INFO("offer[%p]", offer);
+
+       ret = _webrtcbin_create_offer(_webrtc, offer);
+
+       g_mutex_unlock(&_webrtc->mutex);
+
+       return ret;
+}
+
+int webrtc_create_answer(webrtc_h webrtc, char **answer)
+{
+       int ret = WEBRTC_ERROR_NONE;
+       webrtc_s *_webrtc = (webrtc_s*)webrtc;
+
+       RET_VAL_IF(_webrtc == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "webrtc is NULL");
+       RET_VAL_IF(answer == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "answer is NULL");
+
+       g_mutex_lock(&_webrtc->mutex);
+
+       RET_VAL_WITH_UNLOCK_IF(_webrtc->state != WEBRTC_STATE_PLAYING, WEBRTC_ERROR_INVALID_STATE, &_webrtc->mutex, "the state should be PLAYING");
+       /* FIXME: remote description should be set before this API */
+
+       LOG_INFO("answer[%p]", answer);
+
+       ret = _webrtcbin_create_answer(_webrtc, answer);
+
+       g_mutex_unlock(&_webrtc->mutex);
+
+       return ret;
+}
\ No newline at end of file
index 52d8b8fd38bc08966c34873f9acca231ab7a71e7..13fa87918ad96e47baf4031e55a6c811ae7cf267 100644 (file)
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <json-glib/json-glib.h>
 #include "webrtc.h"
 #include "webrtc_private.h"
 
@@ -61,6 +62,58 @@ do { \
        } \
 } while (0)
 
+/* Use g_free() to free the return value. */
+static gchar* __get_string_from_json_object(JsonObject *object)
+{
+       JsonNode *root;
+       JsonGenerator *generator;
+       gchar *text;
+
+       RET_VAL_IF(object == NULL, NULL, "object is NULL");
+
+       root = json_node_init_object(json_node_alloc(), object);
+       generator = json_generator_new();
+       json_generator_set_root(generator, root);
+       text = json_generator_to_data(generator, NULL);
+
+       g_object_unref(generator);
+       json_node_free(root);
+
+       return text;
+}
+
+static gchar* __make_sdp_message(GstWebRTCSessionDescription *desc)
+{
+       gchar *text;
+       JsonObject *msg, *sdp;
+
+       text = gst_sdp_message_as_text(desc->sdp);
+       sdp = json_object_new();
+
+       if (desc->type == GST_WEBRTC_SDP_TYPE_OFFER) {
+               LOG_INFO("making offer message:\n%s", text);
+               json_object_set_string_member(sdp, "type", "offer");
+       } else if (desc->type == GST_WEBRTC_SDP_TYPE_ANSWER) {
+               LOG_INFO("making answer message:\n%s", text);
+               json_object_set_string_member(sdp, "type", "answer");
+       } else {
+               LOG_ERROR("invalid description type");
+               return NULL;
+       }
+
+       json_object_set_string_member(sdp, "sdp", text);
+       g_free(text);
+
+       msg = json_object_new();
+       json_object_set_object_member(msg, "sdp", sdp);
+
+       text = __get_string_from_json_object(msg);
+
+       json_object_unref(msg);
+
+       return text;
+}
+
 static gboolean __meet_gst_state(webrtc_state_e state, GstState gst_state)
 {
        if (state == WEBRTC_STATE_IDLE && gst_state == GST_STATE_READY)
@@ -438,6 +491,14 @@ void _gst_destroy_pipeline(webrtc_s *webrtc)
                g_free(webrtc->stun_server_url);
                webrtc->stun_server_url = NULL;
        }
+       if (webrtc->desc_offer) {
+               g_free(webrtc->desc_offer);
+               webrtc->desc_offer = NULL;
+       }
+       if (webrtc->desc_answer) {
+               g_free(webrtc->desc_answer);
+               webrtc->desc_answer = NULL;
+       }
 }
 
 int _gst_pipeline_set_state(webrtc_s *webrtc, GstState state)
@@ -865,3 +926,132 @@ int _remove_media_source(webrtc_s *webrtc, unsigned int source_id)
 
        return ret;
 }
+
+void _webrtcbin_on_negotiation_needed(GstElement *webrtcbin, gpointer user_data)
+{
+       webrtc_s *webrtc = (webrtc_s *)user_data;
+
+       RET_IF(webrtcbin == NULL, "webrtcbin is NULL");
+       RET_IF(webrtc == NULL, "webrtc is NULL");
+
+       if (webrtc->negotiation_needed_cb.callback == NULL) {
+               LOG_DEBUG("negotiation_needed_cb is NULL, skip it");
+               return;
+       }
+
+       LOG_DEBUG(">>> invoke negotiation_needed_cb[%p], user_data[%p]",
+               webrtc->negotiation_needed_cb.callback, webrtc->negotiation_needed_cb.user_data);
+       ((webrtc_negotiation_needed_cb)(webrtc->negotiation_needed_cb.callback))((webrtc_h)webrtc, webrtc->negotiation_needed_cb.user_data);
+       LOG_DEBUG("<<< end of the callback");
+}
+
+static void __update_session_description(GstPromise *promise, gboolean is_offer, gpointer user_data)
+{
+       GstWebRTCSessionDescription *desc = NULL;
+       const GstStructure *reply;
+       webrtc_s *webrtc = (webrtc_s *)user_data;
+       gchar *sdp_msg;
+
+       RET_IF(promise == NULL, "promise is NULL");
+       RET_IF(webrtc == NULL, "webrtc is NULL");
+       RET_IF(gst_promise_wait(promise) != GST_PROMISE_RESULT_REPLIED, "promise is not for replied result");
+
+       reply = gst_promise_get_reply(promise);
+       gst_structure_get(reply, is_offer ? "offer" : "answer", GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &desc, NULL);
+       gst_promise_unref(promise);
+
+       sdp_msg = __make_sdp_message(desc);
+       gst_webrtc_session_description_free(desc);
+       if (!sdp_msg) {
+               LOG_ERROR("sdp_msg is NULL");
+               return;
+       }
+
+       if (is_offer) {
+               g_free(webrtc->desc_offer);
+               webrtc->desc_offer = sdp_msg;
+       } else {
+               g_free(webrtc->desc_answer);
+               webrtc->desc_answer = sdp_msg;
+       }
+}
+
+static void __offer_created_cb(GstPromise *promise, gpointer user_data)
+{
+       webrtc_s *webrtc = (webrtc_s *)user_data;
+
+       RET_IF(promise == NULL, "promise is NULL");
+       RET_IF(webrtc == NULL, "webrtc is NULL");
+
+       LOG_DEBUG_ENTER();
+
+       __update_session_description(promise, TRUE, webrtc);
+
+       g_cond_signal(&webrtc->desc_cond);
+
+       LOG_DEBUG_LEAVE();
+}
+
+static void __answer_created_cb(GstPromise *promise, gpointer user_data)
+{
+       webrtc_s *webrtc = (webrtc_s *)user_data;
+
+       RET_IF(promise == NULL, "promise is NULL");
+       RET_IF(webrtc == NULL, "webrtc is NULL");
+
+       LOG_DEBUG_ENTER();
+
+       __update_session_description(promise, FALSE, webrtc);
+
+       g_cond_signal(&webrtc->desc_cond);
+
+       LOG_DEBUG_LEAVE();
+}
+
+static int _create_session_description(webrtc_s *webrtc, gboolean is_offer, char **desc)
+{
+       GstPromise *promise;
+       gint64 end_time;
+
+       RET_VAL_IF(webrtc == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "webrtc is NULL");
+       RET_VAL_IF(desc == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "desc is NULL");
+       RET_VAL_IF(webrtc->gst.webrtcbin == NULL, WEBRTC_ERROR_INVALID_OPERATION, "webrtcbin is NULL");
+
+       g_mutex_lock(&webrtc->desc_mutex);
+
+       promise = gst_promise_new_with_change_func(is_offer ? __offer_created_cb : __answer_created_cb, webrtc, NULL);
+       g_signal_emit_by_name(G_OBJECT(webrtc->gst.webrtcbin), is_offer ? "create-offer" : "create-answer", NULL, promise);
+
+       end_time = g_get_monotonic_time() + 10 * G_TIME_SPAN_SECOND;
+       if (!g_cond_wait_until(&webrtc->desc_cond, &webrtc->desc_mutex, end_time)) {
+               g_mutex_unlock(&webrtc->desc_mutex);
+               LOG_ERROR("timeout of g_cond_wait_until()");
+               return WEBRTC_ERROR_INVALID_OPERATION;
+       }
+
+       *desc = is_offer ? strdup(webrtc->desc_offer) : strdup(webrtc->desc_answer);
+
+       g_mutex_unlock(&webrtc->desc_mutex);
+
+       LOG_INFO("%s", *desc);
+
+       return WEBRTC_ERROR_NONE;
+}
+
+int _webrtcbin_create_offer(webrtc_s *webrtc, char **offer)
+{
+       RET_VAL_IF(webrtc == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "webrtc is NULL");
+       RET_VAL_IF(offer == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "offer is NULL");
+       RET_VAL_IF(webrtc->gst.webrtcbin == NULL, WEBRTC_ERROR_INVALID_OPERATION, "webrtcbin is NULL");
+
+       return _create_session_description(webrtc, TRUE, offer);
+}
+
+int _webrtcbin_create_answer(webrtc_s *webrtc, char **answer)
+{
+       RET_VAL_IF(webrtc == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "webrtc is NULL");
+       RET_VAL_IF(answer == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "answer is NULL");
+       RET_VAL_IF(webrtc->gst.webrtcbin == NULL, WEBRTC_ERROR_INVALID_OPERATION, "webrtcbin is NULL");
+
+       return _create_session_description(webrtc, FALSE, answer);
+}
index 8b4faae877c671f0b10d49bb14f92ca2d25f6dba..f7465232e8ddb744d4a88d8945be1900aa6230fb 100644 (file)
@@ -338,6 +338,34 @@ static void _webrtc_unset_negotiation_needed_cb()
                g_print("webrtc_unset_negotiation_needed_cb() success\n");
 }
 
+static void _webrtc_create_offer()
+{
+       int ret = WEBRTC_ERROR_NONE;
+       char *offer = NULL;
+
+       ret = webrtc_create_offer(g_webrtc, &offer);
+       if (ret != WEBRTC_ERROR_NONE) {
+               g_print("failed to webrtc_create_offer()\n");
+       } else {
+               g_print("webrtc_create_offer() success\noffer:\n%s\n", offer);
+               free(offer);
+       }
+}
+
+static void _webrtc_create_answer()
+{
+       int ret = WEBRTC_ERROR_NONE;
+       char *answer = NULL;
+
+       ret = webrtc_create_answer(g_webrtc, &answer);
+       if (ret != WEBRTC_ERROR_NONE) {
+               g_print("failed to webrtc_create_answer()\n");
+       } else {
+               g_print("webrtc_create_answer() success\nanswer:\n%s\n", answer);
+               free(answer);
+       }
+}
+
 static void _setting_signalling_server(char *uri)
 {
        int ret = 0;
@@ -417,6 +445,12 @@ void _interpret_main_menu(char *cmd)
                } else if (strncmp(cmd, "un", 2) == 0) {
                        _webrtc_unset_negotiation_needed_cb();
 
+               } else if (strncmp(cmd, "co", 2) == 0) {
+                       _webrtc_create_offer();
+
+               } else if (strncmp(cmd, "ca", 2) == 0) {
+                       _webrtc_create_answer();
+
                } else {
                        g_print("unknown menu \n");
                }
@@ -441,6 +475,8 @@ void display_sub_basic()
        g_print("r. Remove media source\n");
        g_print("sn. Set negotiation needed callback\t");
        g_print("un. Unset negotiation needed callback\n");
+       g_print("co. Create offer\t");
+       g_print("ca. Create answer\n");
        g_print("st. Set STUN server\n");
        g_print("----------------------------------- App. Setting ----------------------------------------\n");
        g_print("ss. Signalling server\n");