From: Sangchul Lee Date: Wed, 9 Sep 2020 04:56:06 +0000 (+0900) Subject: Add API set for ICE candidate X-Git-Tag: submit/tizen/20210729.023123~223 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=e52e1194f903f4e82ec9c08af1a6a04d43294b51;p=platform%2Fcore%2Fapi%2Fwebrtc.git Add API set for ICE candidate These correspond to the 'onicecandidate' property and 'addIceCandidate' method of the RTCPeerConnection respectively. Functions are added as below. - webrtc_set_ice_candidate_cb() - webrtc_unset_ice_candidate_cb() - webrtc_add_ice_candidate() Test cases for these functions are added to webrtc_test. Some release handle information are added to @remarks. [Version] 0.1.19 [Issue Type] API Change-Id: Ib6675943d3aa2917360b8de82a4e76700089c961 Signed-off-by: Sangchul Lee --- diff --git a/include/webrtc.h b/include/webrtc.h index cfcaea91..1af37c05 100644 --- a/include/webrtc.h +++ b/include/webrtc.h @@ -83,7 +83,7 @@ typedef enum { /** * @brief Called when an error occurs. * @since_tizen 6.0 - * @remarks The @a webrtc is the same object for which the callback was set. + * @remarks The @a webrtc is the same object for which the callback was set.\nThe @a webrtc should not be released. * @param [in] webrtc WebRTC handle * @param [in] error The error code * @param [in] state The current state of the WebRTC handle @@ -96,7 +96,7 @@ typedef void (*webrtc_error_cb)(webrtc_h webrtc, webrtc_error_e error, webrtc_st /** * @brief Called when the WebRTC state is changed. * @since_tizen 6.0 - * @remarks The @a webrtc is the same object for which the callback was set. + * @remarks The @a webrtc is the same object for which the callback was set.\nThe @a webrtc should not be released. * @param [in] webrtc WebRTC handle * @param [in] previous The previous state of the WebRTC handle * @param [in] current The current state of the WebRTC handle @@ -109,6 +109,7 @@ typedef void (*webrtc_state_changed_cb)(webrtc_h webrtc, webrtc_state_e previous /** * @brief Called when the WebRTC needs session negotiation. * @since_tizen 6.0 + * @remarks The @a webrtc is the same object for which the callback was set.\nThe @a webrtc should not be released. * @param [in] webrtc WebRTC handle * @param [in] user_data The user data passed from the callback registration function * @see webrtc_set_negotiation_needed_cb() @@ -116,6 +117,18 @@ typedef void (*webrtc_state_changed_cb)(webrtc_h webrtc, webrtc_state_e previous */ typedef void (*webrtc_negotiation_needed_cb)(webrtc_h webrtc, void *user_data); +/** + * @brief Called when the WebRTC needs to send the ICE candidate message to the remote peer through the signaling channel. + * @since_tizen 6.0 + * @remarks The @a webrtc is the same object for which the callback was set.\nThe @a webrtc should not be released. + * @param [in] webrtc WebRTC handle + * @param [in] candidate The ICE candidate message + * @param [in] user_data The user data passed from the callback registration function + * @see webrtc_set_ice_candidate_cb() + * @see webrtc_unset_ice_candidate_cb() + */ +typedef void (*webrtc_ice_candidate_cb)(webrtc_h webrtc, const char *candidate, void *user_data); + /** * @brief Sets a callback function to be invoked when an asynchronous operation error occurs. * @since_tizen 6.0 @@ -331,6 +344,38 @@ int webrtc_set_negotiation_needed_cb(webrtc_h webrtc, webrtc_negotiation_needed_ */ int webrtc_unset_negotiation_needed_cb(webrtc_h webrtc); +/** + * @brief Sets an ICE candidate callback function to be invoked when the WebRTC needs to send the ICE candidate message to the remote peer. + * @since_tizen 6.0 + * @param [in] webrtc WebRTC handle + * @param [in] callback Callback function pointer + * @param [in] user_data The user data to be passed to the callback function + * @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_IDLE. + * @post webrtc_ice_candidate_cb() will be invoked. + * @see webrtc_unset_ice_candidate_cb() + * @see webrtc_ice_candidate_cb() + */ +int webrtc_set_ice_candidate_cb(webrtc_h webrtc, webrtc_ice_candidate_cb callback, void *user_data); + +/** + * @brief Unsets the ice candidate callback function. + * @since_tizen 6.0 + * @param [in] webrtc WebRTC handle + * @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 + * @see webrtc_set_ice_candidate_cb() + */ +int webrtc_unset_ice_candidate_cb(webrtc_h webrtc); + /** * @brief Creates SDP offer to start a new WebRTC connection to a remote peer. * @since_tizen 6.0 @@ -402,6 +447,23 @@ int webrtc_set_local_description(webrtc_h webrtc, const char *description); */ int webrtc_set_remote_description(webrtc_h webrtc, const char *description); +/** + * @brief Adds a new ICE candidate from the remote peer over its signaling channel. + * @since_tizen 6.0 + * @remarks @a candidate is a JSON string.\n + * It will be {"ice":{"candidate":"..."}}. + * @param [in] webrtc WebRTC handle + * @param [in] candidate The ICE candidate + * @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_add_ice_candidate(webrtc_h webrtc, const char *candidate); + /** * @} */ diff --git a/include/webrtc_private.h b/include/webrtc_private.h index 424c437e..02294c99 100644 --- a/include/webrtc_private.h +++ b/include/webrtc_private.h @@ -152,6 +152,7 @@ typedef struct _webrtc_s { webrtc_callbacks_s error_cb; webrtc_callbacks_s state_changed_cb; webrtc_callbacks_s negotiation_needed_cb; + webrtc_callbacks_s ice_candidate_cb; } webrtc_s; typedef struct _element_info_s { @@ -176,6 +177,7 @@ 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); int _webrtcbin_set_session_description(webrtc_s *webrtc, const char *description, gboolean is_remote); +int _webrtcbin_add_ice_candidate(webrtc_s *webrtc, const char *candidate); #ifdef __cplusplus } diff --git a/packaging/capi-media-webrtc.spec b/packaging/capi-media-webrtc.spec index 06ebc89a..9f618303 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.18 +Version: 0.1.19 Release: 0 Group: Multimedia/API License: Apache-2.0 diff --git a/src/webrtc.c b/src/webrtc.c index f4d86fb8..29490e9c 100644 --- a/src/webrtc.c +++ b/src/webrtc.c @@ -326,6 +326,49 @@ int webrtc_unset_negotiation_needed_cb(webrtc_h webrtc) return WEBRTC_ERROR_NONE; } +int webrtc_set_ice_candidate_cb(webrtc_h webrtc, webrtc_ice_candidate_cb callback, void *user_data) +{ + webrtc_s *_webrtc = (webrtc_s*)webrtc; + + RET_VAL_IF(_webrtc == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "webrtc is NULL"); + RET_VAL_IF(callback == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "callback is NULL"); + + g_mutex_lock(&_webrtc->mutex); + + RET_VAL_WITH_UNLOCK_IF(_webrtc->state != WEBRTC_STATE_IDLE, WEBRTC_ERROR_INVALID_STATE, &_webrtc->mutex, "the state should be IDLE"); + + _webrtc->ice_candidate_cb.callback = callback; + _webrtc->ice_candidate_cb.user_data = user_data; + + LOG_INFO("callback[%p] user_data[%p]", callback, user_data); + + g_mutex_unlock(&_webrtc->mutex); + + return WEBRTC_ERROR_NONE; +} + +int webrtc_unset_ice_candidate_cb(webrtc_h webrtc) +{ + webrtc_s *_webrtc = (webrtc_s*)webrtc; + + RET_VAL_IF(_webrtc == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "webrtc is NULL"); + + g_mutex_lock(&_webrtc->mutex); + + RET_VAL_WITH_UNLOCK_IF(_webrtc->state != WEBRTC_STATE_IDLE, WEBRTC_ERROR_INVALID_STATE, &_webrtc->mutex, "the state should be IDLE"); + RET_VAL_WITH_UNLOCK_IF(_webrtc->ice_candidate_cb.callback == NULL, WEBRTC_ERROR_INVALID_OPERATION, &_webrtc->mutex, "callback was not set"); + + LOG_INFO("callback[%p] user_data[%p] is reset to NULL", + _webrtc->ice_candidate_cb.callback, _webrtc->ice_candidate_cb.user_data); + + _webrtc->ice_candidate_cb.callback = NULL; + _webrtc->ice_candidate_cb.user_data = NULL; + + g_mutex_unlock(&_webrtc->mutex); + + return WEBRTC_ERROR_NONE; +} + int webrtc_create_offer(webrtc_h webrtc, char **offer) { int ret = WEBRTC_ERROR_NONE; @@ -409,4 +452,25 @@ int webrtc_set_remote_description(webrtc_h webrtc, const char *description) g_mutex_unlock(&_webrtc->mutex); return ret; -} \ No newline at end of file +} + +int webrtc_add_ice_candidate(webrtc_h webrtc, const char *candidate) +{ + 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(candidate == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "candidate 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("candidate[ %s ]", candidate); + + ret = _webrtcbin_add_ice_candidate(webrtc, candidate); + + g_mutex_unlock(&_webrtc->mutex); + + return ret; +} diff --git a/src/webrtc_private.c b/src/webrtc_private.c index 0ed7b074..2f6a75e5 100644 --- a/src/webrtc_private.c +++ b/src/webrtc_private.c @@ -62,7 +62,7 @@ do { \ } \ } while (0) -/* Use g_free() to free the return value. */ +/* Use g_free() to release the return value. */ static gchar* __get_string_from_json_object(JsonObject *object) { JsonNode *root; @@ -82,6 +82,7 @@ static gchar* __get_string_from_json_object(JsonObject *object) return text; } +/* Use g_free() to release the return value. */ static gchar* __make_sdp_message(GstWebRTCSessionDescription *desc) { gchar *text; @@ -114,6 +115,31 @@ static gchar* __make_sdp_message(GstWebRTCSessionDescription *desc) return text; } +/* Use g_free() to release the return value. */ +static gchar *__make_ice_candidate_message(guint mlineindex, gchar *candidate) +{ + JsonObject *ice, *msg; + gchar *text; + + if (!candidate) { + LOG_DEBUG("candidate is NULL, all ICE candidates have been sent"); + return NULL; + } + + ice = json_object_new(); + json_object_set_string_member(ice, "candidate", candidate); + json_object_set_int_member(ice, "sdpMLineIndex", mlineindex); + + msg = json_object_new(); + json_object_set_object_member(msg, "ice", ice); + + 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) @@ -441,6 +467,31 @@ static void __webrtcbin_on_negotiation_needed(GstElement *webrtcbin, gpointer us LOG_DEBUG("<<< end of the callback"); } +static void __webrtcbin_on_ice_candidate(GstElement *webrtcbin, guint mlineindex, gchar *candidate, gpointer user_data) +{ + webrtc_s *webrtc = (webrtc_s *)user_data; + gchar *_candidate; + char *_param_candidate; + + RET_IF(webrtcbin == NULL, "webrtcbin is NULL"); + RET_IF(webrtc == NULL, "webrtc is NULL"); + RET_IF(webrtc->ice_candidate_cb.callback == NULL, "ice_candidate_cb is NULL"); + + _candidate = __make_ice_candidate_message(mlineindex, candidate); + if (!_candidate) + return; + + _param_candidate = strdup(_candidate); + g_free(_candidate); + + LOG_DEBUG(">>> invoke ice_candidate_cb[%p], user_data[%p]", + webrtc->ice_candidate_cb.callback, webrtc->ice_candidate_cb.user_data); + ((webrtc_ice_candidate_cb)(webrtc->ice_candidate_cb.callback))((webrtc_h)webrtc, (const char *)_param_candidate, webrtc->ice_candidate_cb.user_data); + LOG_DEBUG("<<< end of the callback"); + + free(_param_candidate); +} + int _gst_build_pipeline(webrtc_s *webrtc) { RET_VAL_IF(webrtc == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "webrtc is NULL"); @@ -463,6 +514,7 @@ int _gst_build_pipeline(webrtc_s *webrtc) goto error; } __connect_and_append_signal(&webrtc->signals, webrtc->gst.webrtcbin, "on-negotiation-needed", G_CALLBACK(__webrtcbin_on_negotiation_needed), webrtc); + __connect_and_append_signal(&webrtc->signals, webrtc->gst.webrtcbin, "on-ice-candidate", G_CALLBACK(__webrtcbin_on_ice_candidate), webrtc); if (!gst_bin_add(GST_BIN(webrtc->gst.pipeline), webrtc->gst.webrtcbin)) { LOG_ERROR("failed to gst_bin_add(), [%s] -> [%s] pipeline", GST_ELEMENT_NAME(webrtc->gst.webrtcbin), GST_ELEMENT_NAME(webrtc->gst.pipeline)); @@ -1076,7 +1128,7 @@ int _webrtcbin_create_answer(webrtc_s *webrtc, char **answer) return _create_session_description(webrtc, FALSE, answer); } -/* Use g_free() to free the sdp and type parameter. */ +/* Use g_free() to release the sdp and type parameter. */ static int __get_sdp_from_description(const char *description, gchar **sdp, gchar **type) { int ret = WEBRTC_ERROR_NONE; @@ -1192,3 +1244,82 @@ end: return ret; } +/* Use g_free() to release the candidate parameter. */ +static int __get_ice_candidate_from_message(const char *ice_message, gchar **candidate, gint *mlineindex) +{ + int ret = WEBRTC_ERROR_NONE; + JsonNode *root; + JsonObject *object; + JsonObject *child; + JsonParser *parser; + const gchar *_candidate; + + RET_VAL_IF(ice_message == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "ice_message is NULL"); + RET_VAL_IF(candidate == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "candidate is NULL"); + RET_VAL_IF(mlineindex == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "mlineindex is NULL"); + + parser = json_parser_new(); + if (!JSON_IS_PARSER(parser)) + return WEBRTC_ERROR_INVALID_OPERATION; + + if (!json_parser_load_from_data(parser, ice_message, -1, NULL)) { + LOG_ERROR("unknown message: %s", ice_message); + ret = WEBRTC_ERROR_INVALID_PARAMETER; + goto end; + } + + root = json_parser_get_root(parser); + if (!JSON_NODE_HOLDS_OBJECT(root)) { + LOG_ERROR("it does not contain a JsonObject: %s", ice_message); + ret = WEBRTC_ERROR_INVALID_PARAMETER; + goto end; + } + + object = json_node_get_object(root); + if (!json_object_has_member(object, "ice")) { + LOG_ERROR("It does not contain 'ice' member: %s", ice_message); + ret = WEBRTC_ERROR_INVALID_PARAMETER; + goto end; + } + + child = json_object_get_object_member(object, "ice"); + + _candidate = json_object_get_string_member(child, "candidate"); + if (!_candidate) { + LOG_ERROR("Could not find candidate member: %s", ice_message); + ret = WEBRTC_ERROR_INVALID_PARAMETER; + goto end; + } + + *candidate = g_strdup(_candidate); + *mlineindex = json_object_get_int_member(child, "sdpMLineIndex"); + + LOG_DEBUG("candidate: %s", *candidate); + LOG_DEBUG("sdpMLineIndex: %d", *mlineindex); +end: + g_object_unref(parser); + return ret; +} + +int _webrtcbin_add_ice_candidate(webrtc_s *webrtc, const char *candidate) +{ + int ret = WEBRTC_ERROR_NONE; + gchar *_candidate; + gint sdpmlineindex; + + RET_VAL_IF(webrtc == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "webrtc is NULL"); + RET_VAL_IF(candidate == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "candidate is NULL"); + RET_VAL_IF(webrtc->gst.webrtcbin == NULL, WEBRTC_ERROR_INVALID_OPERATION, "webrtcbin is NULL"); + + ret = __get_ice_candidate_from_message(candidate, &_candidate, &sdpmlineindex); + if (ret != WEBRTC_ERROR_NONE) + return ret; + + g_signal_emit_by_name(webrtc->gst.webrtcbin, "add-ice-candidate", sdpmlineindex, _candidate); + + LOG_DEBUG("[add-ice-candidate] signal is emitted"); + + g_free(_candidate); + + return ret; +} diff --git a/test/webrtc_test.c b/test/webrtc_test.c index eb499564..ab680e4b 100644 --- a/test/webrtc_test.c +++ b/test/webrtc_test.c @@ -75,6 +75,7 @@ static webrtc_h g_webrtc; static char *g_offer; static char *g_answer; static char *g_remote_desc; +static GList *g_ice_candidates; static SoupWebsocketConnection *g_ws_conn; static gint32 g_local_peer_id; static gchar g_signaling_server[MAX_STRING_LEN]; @@ -419,6 +420,36 @@ static void _webrtc_unset_negotiation_needed_cb() g_print("webrtc_unset_negotiation_needed_cb() success\n"); } +static void __ice_candidate_cb(webrtc_h webrtc, const char *candidate, void *user_data) +{ + g_print("__ice_candidate_cb() is invoked\n"); + + g_print("\n[to SERVER > ICE]\n%s\n", candidate); + soup_websocket_connection_send_text(g_ws_conn, candidate); +} + +static void _webrtc_set_ice_candidate_cb() +{ + int ret = WEBRTC_ERROR_NONE; + + ret = webrtc_set_ice_candidate_cb(g_webrtc, __ice_candidate_cb, g_webrtc); + if (ret != WEBRTC_ERROR_NONE) + g_print("failed to webrtc_set_ice_candidate_cb()\n"); + else + g_print("webrtc_set_ice_candidate_cb() success\n"); +} + +static void _webrtc_unset_ice_candidate_cb() +{ + int ret = WEBRTC_ERROR_NONE; + + ret = webrtc_unset_ice_candidate_cb(g_webrtc); + if (ret != WEBRTC_ERROR_NONE) + g_print("failed to webrtc_unset_ice_candidate_cb()\n"); + else + g_print("webrtc_unset_ice_candidate_cb() success\n"); +} + static void _webrtc_create_offer() { int ret = WEBRTC_ERROR_NONE; @@ -473,6 +504,24 @@ static void _webrtc_set_remote_description(void) g_print("webrtc_set_remote_description() success\n"); } +static void __foreach_ice_candidate(gpointer data, gpointer user_data) +{ + int ret = WEBRTC_ERROR_NONE; + + ret = webrtc_add_ice_candidate(g_webrtc, (const char *)data); + if (ret != WEBRTC_ERROR_NONE) + g_print("failed to webrtc_ice_candidate()\n"); + else + g_print("webrtc_ice_candidate() success\n"); +} + +static void _webrtc_add_ice_candidate(void) +{ + g_list_foreach(g_ice_candidates, __foreach_ice_candidate, NULL); + + g_list_free_full(g_ice_candidates, free); +} + static void _setting_uri(gchar *dest_arr, char *uri) { int ret = 0; @@ -582,7 +631,7 @@ static void __handle_json_structured_message(const gchar *text) } else if (json_object_has_member(object, "ice")){ g_print("\n[from SERVER > ICE]\n%s\n", text); - /* FIXME: add ice candidates, several candidates will arrive from the server */ + g_ice_candidates = g_list_append(g_ice_candidates, strdup(text)); } else { g_printerr("neither 'sdp' nor 'ice' member exist in JSON message [%s]\n", text); @@ -708,6 +757,7 @@ void quit_program() free(g_offer); free(g_answer); free(g_remote_desc); + g_list_free_full(g_ice_candidates, free); elm_exit(); } @@ -759,6 +809,12 @@ void _interpret_main_menu(char *cmd) } else if (strncmp(cmd, "un", 2) == 0) { _webrtc_unset_negotiation_needed_cb(); + } else if (strncmp(cmd, "si", 2) == 0) { + _webrtc_set_ice_candidate_cb(); + + } else if (strncmp(cmd, "ui", 2) == 0) { + _webrtc_unset_ice_candidate_cb(); + } else if (strncmp(cmd, "co", 2) == 0) { _webrtc_create_offer(); @@ -789,6 +845,9 @@ void _interpret_main_menu(char *cmd) } else if (strncmp(cmd, "sd", 2) == 0) { g_menu_state = CURRENT_STATUS_SEND_LOCAL_DESCRIPTION; + } else if (strncmp(cmd, "ac", 2) == 0) { + _webrtc_add_ice_candidate(); + } else { g_print("unknown menu \n"); } @@ -834,10 +893,13 @@ void display_sub_basic() g_print("us. Unset state changed callback\n"); g_print("sn. Set negotiation needed callback\t"); g_print("un. Unset negotiation needed callback\n"); + g_print("si. Set ICE candidate callback\t"); + g_print("ui. Unset ICE candidate callback\n"); g_print("co. Create offer\t"); g_print("ca. Create answer\n"); g_print("sl. Set local description\t"); g_print("sr. Set remote description\n"); + g_print("ac. Add ICE candidate\n"); g_print("st. Set STUN server\n"); g_print("----------------------------------- App. Setting ----------------------------------------\n"); display_setting_status();