From: Sangchul Lee Date: Tue, 1 Sep 2020 09:32:31 +0000 (+0900) Subject: Add webrtc_create_offer() and webrtc_create_answer() API X-Git-Tag: submit/tizen/20210729.023123~233 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=5e7dbdc225c891747d70848fb0cb309237b9673b;p=platform%2Fcore%2Fapi%2Fwebrtc.git Add webrtc_create_offer() and webrtc_create_answer() API 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 --- diff --git a/CMakeLists.txt b/CMakeLists.txt index 354f6a63..6757b966 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/include/webrtc.h b/include/webrtc.h index b2867c0f..4a1de933 100644 --- a/include/webrtc.h +++ b/include/webrtc.h @@ -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); + /** * @} */ diff --git a/include/webrtc_private.h b/include/webrtc_private.h index 1509645e..47bf9e61 100644 --- a/include/webrtc_private.h +++ b/include/webrtc_private.h @@ -20,6 +20,10 @@ #include #include #include +#ifndef GST_USE_UNSTABLE_API +#define GST_USE_UNSTABLE_API +#include +#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 */ diff --git a/packaging/capi-media-webrtc.spec b/packaging/capi-media-webrtc.spec index 5c04bc7f..0b5a3baa 100644 --- a/packaging/capi-media-webrtc.spec +++ b/packaging/capi-media-webrtc.spec @@ -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. diff --git a/src/webrtc.c b/src/webrtc.c index 335b0622..cca116e1 100644 --- a/src/webrtc.c +++ b/src/webrtc.c @@ -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 diff --git a/src/webrtc_private.c b/src/webrtc_private.c index 52d8b8fd..13fa8791 100644 --- a/src/webrtc_private.c +++ b/src/webrtc_private.c @@ -14,6 +14,7 @@ * limitations under the License. */ +#include #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); +} diff --git a/test/webrtc_test.c b/test/webrtc_test.c index 8b4faae8..f7465232 100644 --- a/test/webrtc_test.c +++ b/test/webrtc_test.c @@ -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");