From: Sangchul Lee Date: Tue, 29 Sep 2020 06:13:34 +0000 (+0900) Subject: Add API set for data channel X-Git-Tag: submit/tizen/20210729.023123~202 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=676cca1b783c01759debd7cc088d4328fc11ebfc;p=platform%2Fcore%2Fapi%2Fwebrtc.git Add API set for data channel These correspond to methods and event handlers of the RTCPeerConnection and RTCDataChannel as below. - RTCPeerConnection: createDataChannel(), ondatachannel - RTCDataChannel: send(), onopen, onclose, onerror, onmessage Functions are added as below. - webrtc_set[unset]_data_channel_cb() - webrtc_create[destroy]_data_channel() - webrtc_data_channel_set[unset]_open_cb() - webrtc_data_channel_set[unset]_message_cb() - webrtc_data_channel_set[unset]_error_cb() - webrtc_data_channel_set[unset]_close_cb() - webrtc_data_channel_send_string() Test cases for these functions are added to webrtc_test. [Version] 0.1.40 [Issue Type] API Change-Id: Ic03a03499de2e44475469b119d2fa8d2f3b72e03 Signed-off-by: Sangchul Lee --- diff --git a/include/webrtc.h b/include/webrtc.h index 641c6126..ed575c5a 100644 --- a/include/webrtc.h +++ b/include/webrtc.h @@ -43,6 +43,12 @@ extern "C" { */ typedef void *webrtc_h; +/** + * @brief WebRTC data channel handle type. + * @since_tizen 6.5 + */ +typedef void *webrtc_data_channel_h; + /** * @brief Enumeration for WebRTC error. * @since_tizen 6.5 @@ -102,6 +108,16 @@ typedef enum { WEBRTC_TRANSCEIVER_DIRECTION_SENDRECV, } webrtc_transceiver_direction_e; +/** + * @brief Enumeration for WebRTC data channel type. + * @since_tizen 6.5 + */ +typedef enum +{ + WEBRTC_DATA_CHANNEL_TYPE_STRING, /**< String data */ + WEBRTC_DATA_CHANNEL_TYPE_BYTES, /**< Bytes data */ +} webrtc_data_channel_type_e; + /** * @brief Called when an error occurs. * @details The following error codes can be received:\n @@ -155,6 +171,79 @@ typedef void (*webrtc_negotiation_needed_cb)(webrtc_h webrtc, void *user_data); */ typedef void (*webrtc_ice_candidate_cb)(webrtc_h webrtc, const char *candidate, void *user_data); +/** + * @brief Called when the data channel is created to the connection by the remote peer. + * @since_tizen 6.5 + * @remarks The @a webrtc is the same object for which the callback was set.\nThe @a webrtc should not be released. + * @remarks The @a channel should not be released. + * @param[in] channel WebRTC data channel handle + * @param[in] user_data The user data passed from the callback registration function + * @see webrtc_data_channel_set_open_cb() + * @see webrtc_data_channel_unset_open_cb() + * @see webrtc_data_channel_set_message_cb() + * @see webrtc_data_channel_unset_message_cb() + * @see webrtc_data_channel_set_error_cb() + * @see webrtc_data_channel_unset_error_cb() + * @see webrtc_data_channel_set_close_cb() + * @see webrtc_data_channel_unset_close_cb() + */ +typedef void (*webrtc_data_channel_cb)(webrtc_h webrtc, webrtc_data_channel_h channel, void *user_data); + +/** + * @brief Called when the data channel's underlying data transport is established. + * @since_tizen 6.5 + * @remarks The @a channel is the same object for which the callback was set.\nThe @a channel should not be released. + * @param[in] channel WebRTC data channel handle + * @param[in] user_data The user data passed from the callback registration function + * @see webrtc_data_channel_create() + * @see webrtc_data_channel_destroy() + * @see webrtc_data_channel_set_open_cb() + * @see webrtc_data_channel_unset_open_cb() + */ +typedef void (*webrtc_data_channel_open_cb)(webrtc_data_channel_h channel, void *user_data); + +/** + * @brief Called when a message is received from other peer via the data channel. + * @since_tizen 6.5 + * @remarks The @a channel is the same object for which the callback was set.\nThe @a channel should not be released. + * @param[in] channel WebRTC data channel handle + * @param[in] type The data type + * @param[in] message The messsage from the remote peer + * @param[in] user_data The user data passed from the callback registration function + * @see webrtc_data_channel_create() + * @see webrtc_data_channel_destroy() + * @see webrtc_data_channel_set_message_cb() + * @see webrtc_data_channel_unset_message_cb() + */ +typedef void (*webrtc_data_channel_message_cb)(webrtc_data_channel_h channel, webrtc_data_channel_type_e type, void *message, void *user_data); + +/** + * @brief Called when an error occurs on the data channel. + * @since_tizen 6.5 + * @remarks The @a channel is the same object for which the callback was set.\nThe @a channel should not be released. + * @param[in] channel WebRTC data channel handle + * @param[in] error The error code + * @param[in] user_data The user data passed from the callback registration function + * @see webrtc_data_channel_create() + * @see webrtc_data_channel_destroy() + * @see webrtc_data_channel_set_error_cb() + * @see webrtc_data_channel_unset_error_cb() + */ +typedef void (*webrtc_data_channel_error_cb)(webrtc_data_channel_h channel, webrtc_error_e error, void *user_data); + +/** + * @brief Called when the data channel has closed down. + * @since_tizen 6.5 + * @remarks The @a channel is the same object for which the callback was set.\nThe @a channel should not be released. + * @param[in] channel WebRTC data channel handle + * @param[in] user_data The user data passed from the callback registration function + * @see webrtc_data_channel_create() + * @see webrtc_data_channel_destroy() + * @see webrtc_data_channel_set_close_cb() + * @see webrtc_data_channel_unset_close_cb() + */ +typedef void (*webrtc_data_channel_close_cb)(webrtc_data_channel_h channel, void *user_data); + /** * @brief Sets a callback function to be invoked when an asynchronous operation error occurs. * @since_tizen 6.5 @@ -527,6 +616,198 @@ int webrtc_set_remote_description(webrtc_h webrtc, const char *description); */ int webrtc_add_ice_candidate(webrtc_h webrtc, const char *candidate); +/** + * @brief Sets a data channel callback function to be invoked when the data channel is created to the connection by the remote peer. + * @since_tizen 6.5 + * @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_STATE Invalid state + * @pre @a webrtc state must be set to #WEBRTC_STATE_IDLE. + * @post webrtc_data_channel_cb() will be invoked. + * @see webrtc_unset_data_channel_cb() + * @see webrtc_data_channel_cb() + */ +int webrtc_set_data_channel_cb(webrtc_h webrtc, webrtc_data_channel_cb callback, void *user_data); + +/** + * @brief Unsets the data channel callback function. + * @since_tizen 6.5 + * @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_data_channel_cb() + */ +int webrtc_unset_data_channel_cb(webrtc_h webrtc); + +/** + * @brief Creates a new data channel which is linked with the remote peer. + * @since_tizen 6.5 + * @param[in] webrtc WebRTC handle + * @param[in] label Name for the channel + * @param[out] channel Data channel 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 + * @retval #WEBRTC_ERROR_INVALID_STATE Invalid state + * @pre @a webrtc state must be set to #WEBRTC_STATE_IDLE. + * @see webrtc_destroy_data_channel() + */ +int webrtc_create_data_channel(webrtc_h webrtc, const char *label, webrtc_data_channel_h *channel); + +/** + * @brief Destroys the data channel. + * @since_tizen 6.5 + * @param[in] channel Data channel handle + * @return @c 0 on success, + * otherwise a negative error value + * @retval #WEBRTC_ERROR_NONE Successful + * @retval #WEBRTC_ERROR_INVALID_PARAMETER Invalid parameter + * @see webrtc_create_data_channel() + */ +int webrtc_destroy_data_channel(webrtc_data_channel_h channel); + +/** + * @brief Sets a data channel open callback function to be invoked when the data channel's underlying data transport is established. + * @since_tizen 6.5 + * @param[in] channel Data channel 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 + * @post webrtc_data_channel_open_cb() will be invoked. + * @see webrtc_data_channel_unset_open_cb() + * @see webrtc_data_channel_open_cb() + */ +int webrtc_data_channel_set_open_cb(webrtc_data_channel_h channel, webrtc_data_channel_open_cb callback, void *user_data); + +/** + * @brief Unsets the data channel open callback function. + * @since_tizen 6.5 + * @param[in] channel Data channel 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_data_channel_set_open_cb() + */ +int webrtc_data_channel_unset_open_cb(webrtc_data_channel_h channel); + +/** + * @brief Sets a data channel message callback function to be invoked when a message is received from the remote peer. + * @since_tizen 6.5 + * @param[in] channel Data channel 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 + * @post webrtc_data_channel_message_cb() will be invoked. + * @see webrtc_data_channel_unset_message_cb() + * @see webrtc_data_channel_message_cb() + */ +int webrtc_data_channel_set_message_cb(webrtc_data_channel_h channel, webrtc_data_channel_message_cb callback, void *user_data); + +/** + * @brief Unsets the data channel message callback function. + * @since_tizen 6.5 + * @param[in] channel Data channel 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_data_channel_set_message_cb() + */ +int webrtc_data_channel_unset_message_cb(webrtc_data_channel_h channel); + +/** + * @brief Sets a data channel error callback function to be invoked when an error occurs on the data channel. + * @since_tizen 6.5 + * @param[in] channel Data channel 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 + * @post webrtc_data_channel_error_cb() will be invoked. + * @see webrtc_data_channel_unset_error_cb() + * @see webrtc_data_channel_error_cb() + */ +int webrtc_data_channel_set_error_cb(webrtc_data_channel_h channel, webrtc_data_channel_error_cb callback, void *user_data); + +/** + * @brief Unsets the data channel error callback function. + * @since_tizen 6.5 + * @param[in] channel Data channel 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_data_channel_set_error_cb() + */ +int webrtc_data_channel_unset_error_cb(webrtc_data_channel_h channel); + +/** + * @brief Sets a data channel close callback function to be invoked when the data channel has closed down. + * @since_tizen 6.5 + * @param[in] channel Data channel 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 + * @post webrtc_data_channel_close_cb() will be invoked. + * @see webrtc_data_channel_unset_close_cb() + * @see webrtc_data_channel_close_cb() + */ +int webrtc_data_channel_set_close_cb(webrtc_data_channel_h channel, webrtc_data_channel_close_cb callback, void *user_data); + +/** + * @brief Unsets the data channel close callback function. + * @since_tizen 6.5 + * @param[in] channel Data channel 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_data_channel_set_close_cb() + */ +int webrtc_data_channel_unset_close_cb(webrtc_data_channel_h channel); + +/** + * @brief Sends a string data across the data channel to the remote peer. + * @since_tizen 6.5 + * @param[in] channel Data channel handle + * @param[in] string String data to send + * @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 channel should be opened before calling this function. + * @see webrtc_data_channel_open_cb() + */ +int webrtc_data_channel_send_string(webrtc_data_channel_h channel, const char *string); + /** * @} */ diff --git a/include/webrtc_private.h b/include/webrtc_private.h index 2406bdc5..6bff3699 100644 --- a/include/webrtc_private.h +++ b/include/webrtc_private.h @@ -143,19 +143,24 @@ do { \ g_free(dot_name); \ } while (0) -typedef struct _webrtc_ini_s { - gboolean generate_dot; - gchar **gst_args; -} webrtc_ini_s; - typedef enum { MEDIA_TYPE_AUDIO = 0x01, MEDIA_TYPE_VIDEO = 0x02, } media_type_e; +typedef enum { + DATA_CHANNEL_TYPE_STRING, + DATA_CHANNEL_TYPE_BYTES +} data_channel_type_e; + #define MLINES_IDX_AUDIO 0 #define MLINES_IDX_VIDEO 1 +typedef struct _webrtc_ini_s { + gboolean generate_dot; + gchar **gst_args; +} webrtc_ini_s; + typedef struct _webrtc_gst_slot_s { unsigned int id; GstElement *bin; @@ -196,12 +201,28 @@ typedef struct _webrtc_s { GList *signals; + GHashTable *data_channels; + webrtc_callbacks_s error_cb; webrtc_callbacks_s state_changed_cb; webrtc_callbacks_s negotiation_needed_cb; webrtc_callbacks_s ice_candidate_cb; + webrtc_callbacks_s data_channel_cb; } webrtc_s; +typedef struct _webrtc_data_channel_s { + GMutex mutex; + + GObject *channel; + + GList *signals; + + webrtc_callbacks_s open_cb; + webrtc_callbacks_s message_cb; + webrtc_callbacks_s error_cb; + webrtc_callbacks_s close_cb; +} webrtc_data_channel_s; + typedef struct _element_info_s { const gchar *klass_name; GstCaps *src_caps; @@ -223,7 +244,10 @@ int _remove_media_source(webrtc_s *webrtc, unsigned int source_id); int _get_transceiver_direction(webrtc_s *webrtc, unsigned int source_id, webrtc_media_type_e media_type, webrtc_transceiver_direction_e *direction); int _set_transceiver_direction(webrtc_s *webrtc, unsigned int source_id, webrtc_media_type_e media_type, webrtc_transceiver_direction_e direction); void _post_state_in_idle(webrtc_s *webrtc, webrtc_state_e new_state); +void _invoke_state_changed_cb(webrtc_s *webrtc, webrtc_state_e old, webrtc_state_e new); +void _connect_and_append_signal(GList **signals, GObject *obj, const char *sig_name, GCallback cb, gpointer user_data); +void _disconnect_signal(gpointer data); GstElement *_create_element(const char *factory_name, const char *name); GstElement *_create_element_from_registry(element_info_s *elem_info); int _add_no_target_ghostpad_to_slot(webrtc_gst_slot_s *slot, gboolean is_src, GstPad **new_pad); @@ -237,6 +261,13 @@ 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); +void _webrtcbin_on_data_channel_cb(GstElement *webrtcbin, GObject *data_channel, gpointer user_data); + +void _init_data_channels(webrtc_s *webrtc); +void _destroy_data_channels(webrtc_s *webrtc); +int _create_data_channel(webrtc_s *webrtc, const char *label, webrtc_data_channel_s **channel); +int _destroy_data_channel(webrtc_data_channel_s *channel); +int _data_channel_send_string(webrtc_data_channel_s *channel, const char *string); #ifdef __cplusplus } diff --git a/packaging/capi-media-webrtc.spec b/packaging/capi-media-webrtc.spec index 79f88206..5544d754 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.39 +Version: 0.1.40 Release: 0 Group: Multimedia/API License: Apache-2.0 diff --git a/src/webrtc.c b/src/webrtc.c index 0e2637b4..0c20169b 100644 --- a/src/webrtc.c +++ b/src/webrtc.c @@ -119,6 +119,7 @@ int webrtc_create(webrtc_h *webrtc) _ini_load(_webrtc); _gst_init(_webrtc); _gst_build_pipeline(_webrtc); + _init_data_channels(_webrtc); _webrtc->pend_state = WEBRTC_STATE_IDLE; _webrtc->state = _webrtc->pend_state; @@ -145,6 +146,7 @@ int webrtc_destroy(webrtc_h webrtc) _webrtc->pend_state = WEBRTC_STATE_IDLE; _webrtc->state = _webrtc->pend_state; + _destroy_data_channels(_webrtc); _gst_destroy_pipeline(_webrtc); g_mutex_clear(&_webrtc->desc_mutex); @@ -199,6 +201,7 @@ int webrtc_stop(webrtc_h webrtc) LOG_INFO("webrtc[%p] is stopping", webrtc); g_hash_table_remove_all(_webrtc->gst.sink_slots); + g_hash_table_remove_all(_webrtc->data_channels); g_mutex_unlock(&_webrtc->mutex); @@ -517,3 +520,257 @@ int webrtc_add_ice_candidate(webrtc_h webrtc, const char *candidate) return ret; } + +int webrtc_create_data_channel(webrtc_h webrtc, const char *label, webrtc_data_channel_h *channel) +{ + 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(label == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "label is NULL"); + RET_VAL_IF(channel == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "channel 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"); + + _gst_pipeline_set_state(_webrtc, GST_STATE_READY); + + ret = _create_data_channel(webrtc, label, (webrtc_data_channel_s **)channel); + + g_mutex_unlock(&_webrtc->mutex); + + return ret; +} + +int webrtc_destroy_data_channel(webrtc_data_channel_h channel) +{ + int ret = WEBRTC_ERROR_NONE; + + RET_VAL_IF(channel == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "channel is NULL"); + + ret = _destroy_data_channel((webrtc_data_channel_s *)channel); + + return ret; +} + +int webrtc_set_data_channel_cb(webrtc_h webrtc, webrtc_data_channel_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->data_channel_cb.callback = callback; + _webrtc->data_channel_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_data_channel_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->data_channel_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->data_channel_cb.callback, _webrtc->data_channel_cb.user_data); + + _webrtc->data_channel_cb.callback = NULL; + _webrtc->data_channel_cb.user_data = NULL; + + g_mutex_unlock(&_webrtc->mutex); + + return WEBRTC_ERROR_NONE; +} + +int webrtc_data_channel_set_open_cb(webrtc_data_channel_h channel, webrtc_data_channel_open_cb callback, void *user_data) +{ + webrtc_data_channel_s *_channel = (webrtc_data_channel_s*)channel; + + RET_VAL_IF(_channel == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "channel is NULL"); + RET_VAL_IF(callback == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "callback is NULL"); + + g_mutex_lock(&_channel->mutex); + + _channel->open_cb.callback = callback; + _channel->open_cb.user_data = user_data; + + LOG_INFO("callback[%p] user_data[%p]", callback, user_data); + + g_mutex_unlock(&_channel->mutex); + + return WEBRTC_ERROR_NONE; +} + +int webrtc_data_channel_unset_open_cb(webrtc_data_channel_h channel) +{ + webrtc_data_channel_s *_channel = (webrtc_data_channel_s*)channel; + + RET_VAL_IF(_channel == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "channel is NULL"); + + g_mutex_lock(&_channel->mutex); + + RET_VAL_WITH_UNLOCK_IF(_channel->open_cb.callback == NULL, WEBRTC_ERROR_INVALID_OPERATION, &_channel->mutex, "callback was not set"); + + LOG_INFO("callback[%p] user_data[%p] is reset to NULL", + _channel->open_cb.callback, _channel->open_cb.user_data); + + _channel->open_cb.callback = NULL; + _channel->open_cb.user_data = NULL; + + g_mutex_unlock(&_channel->mutex); + + return WEBRTC_ERROR_NONE; +} + +int webrtc_data_channel_set_message_cb(webrtc_data_channel_h channel, webrtc_data_channel_message_cb callback, void *user_data) +{ + webrtc_data_channel_s *_channel = (webrtc_data_channel_s*)channel; + + RET_VAL_IF(_channel == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "channel is NULL"); + RET_VAL_IF(callback == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "callback is NULL"); + + g_mutex_lock(&_channel->mutex); + + _channel->message_cb.callback = callback; + _channel->message_cb.user_data = user_data; + + LOG_INFO("callback[%p] user_data[%p]", callback, user_data); + + g_mutex_unlock(&_channel->mutex); + + return WEBRTC_ERROR_NONE; +} + +int webrtc_data_channel_unset_message_cb(webrtc_data_channel_h channel) +{ + webrtc_data_channel_s *_channel = (webrtc_data_channel_s*)channel; + + RET_VAL_IF(_channel == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "channel is NULL"); + + g_mutex_lock(&_channel->mutex); + + RET_VAL_WITH_UNLOCK_IF(_channel->message_cb.callback == NULL, WEBRTC_ERROR_INVALID_OPERATION, &_channel->mutex, "callback was not set"); + + LOG_INFO("callback[%p] user_data[%p] is reset to NULL", + _channel->message_cb.callback, _channel->message_cb.user_data); + + _channel->message_cb.callback = NULL; + _channel->message_cb.user_data = NULL; + + g_mutex_unlock(&_channel->mutex); + + return WEBRTC_ERROR_NONE; +} + +int webrtc_data_channel_set_error_cb(webrtc_data_channel_h channel, webrtc_data_channel_error_cb callback, void *user_data) +{ + webrtc_data_channel_s *_channel = (webrtc_data_channel_s*)channel; + + RET_VAL_IF(_channel == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "channel is NULL"); + RET_VAL_IF(callback == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "callback is NULL"); + + g_mutex_lock(&_channel->mutex); + + _channel->error_cb.callback = callback; + _channel->error_cb.user_data = user_data; + + LOG_INFO("callback[%p] user_data[%p]", callback, user_data); + + g_mutex_unlock(&_channel->mutex); + + return WEBRTC_ERROR_NONE; +} + +int webrtc_data_channel_unset_error_cb(webrtc_data_channel_h channel) +{ + webrtc_data_channel_s *_channel = (webrtc_data_channel_s*)channel; + + RET_VAL_IF(_channel == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "channel is NULL"); + + g_mutex_lock(&_channel->mutex); + + RET_VAL_WITH_UNLOCK_IF(_channel->error_cb.callback == NULL, WEBRTC_ERROR_INVALID_OPERATION, &_channel->mutex, "callback was not set"); + + LOG_INFO("callback[%p] user_data[%p] is reset to NULL", + _channel->error_cb.callback, _channel->error_cb.user_data); + + _channel->error_cb.callback = NULL; + _channel->error_cb.user_data = NULL; + + g_mutex_unlock(&_channel->mutex); + + return WEBRTC_ERROR_NONE; +} + +int webrtc_data_channel_set_close_cb(webrtc_data_channel_h channel, webrtc_data_channel_close_cb callback, void *user_data) +{ + webrtc_data_channel_s *_channel = (webrtc_data_channel_s*)channel; + + RET_VAL_IF(_channel == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "channel is NULL"); + RET_VAL_IF(callback == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "callback is NULL"); + + g_mutex_lock(&_channel->mutex); + + _channel->close_cb.callback = callback; + _channel->close_cb.user_data = user_data; + + LOG_INFO("callback[%p] user_data[%p]", callback, user_data); + + g_mutex_unlock(&_channel->mutex); + + return WEBRTC_ERROR_NONE; +} + +int webrtc_data_channel_unset_close_cb(webrtc_data_channel_h channel) +{ + webrtc_data_channel_s *_channel = (webrtc_data_channel_s*)channel; + + RET_VAL_IF(_channel == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "channel is NULL"); + + g_mutex_lock(&_channel->mutex); + + RET_VAL_WITH_UNLOCK_IF(_channel->close_cb.callback == NULL, WEBRTC_ERROR_INVALID_OPERATION, &_channel->mutex, "callback was not set"); + + LOG_INFO("callback[%p] user_data[%p] is reset to NULL", + _channel->close_cb.callback, _channel->close_cb.user_data); + + _channel->close_cb.callback = NULL; + _channel->close_cb.user_data = NULL; + + g_mutex_unlock(&_channel->mutex); + + return WEBRTC_ERROR_NONE; +} + +int webrtc_data_channel_send_string(webrtc_data_channel_h channel, const char *string) +{ + webrtc_data_channel_s *_channel = (webrtc_data_channel_s*)channel; + int ret = WEBRTC_ERROR_NONE; + + RET_VAL_IF(_channel == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "channel is NULL"); + RET_VAL_IF(string == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "string is NULL"); + RET_VAL_IF(_channel->channel == NULL, WEBRTC_ERROR_INVALID_OPERATION, "data_channel is NULL"); + + g_mutex_lock(&_channel->mutex); + + ret = _data_channel_send_string(channel, string); + + g_mutex_unlock(&_channel->mutex); + + return ret; +} \ No newline at end of file diff --git a/src/webrtc_data_channel.c b/src/webrtc_data_channel.c new file mode 100644 index 00000000..be787df6 --- /dev/null +++ b/src/webrtc_data_channel.c @@ -0,0 +1,257 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "webrtc.h" +#include "webrtc_private.h" + +static void __data_channel_on_open_cb(GObject *data_channel, gpointer user_data) +{ + webrtc_data_channel_s *channel = (webrtc_data_channel_s *)user_data; + + RET_IF(channel == NULL, "channel is NULL"); + + LOG_DEBUG("data_channel[%s] user_data[%p]", GST_OBJECT_NAME(data_channel), user_data); + + if (channel->open_cb.callback) { + LOG_DEBUG(">>> callback[%p] user_data[%p]", channel->open_cb.callback, channel->open_cb.user_data); + ((webrtc_data_channel_open_cb)(channel->open_cb.callback))((webrtc_data_channel_h)channel, channel->open_cb.user_data); + LOG_DEBUG("<<< end of the callback"); + } +} + +static void __data_channel_on_message_string_cb(GObject *data_channel, gchar *string, gpointer user_data) +{ + webrtc_data_channel_s *channel = (webrtc_data_channel_s *)user_data; + + RET_IF(channel == NULL, "channel is NULL"); + + LOG_INFO("data_channel[%s] <<<<< [%s]", GST_OBJECT_NAME(data_channel), string); + + if (channel->message_cb.callback) { + LOG_DEBUG(">>> callback[%p] user_data[%p]", channel->message_cb.callback, channel->message_cb.user_data); + ((webrtc_data_channel_message_cb)(channel->message_cb.callback))((webrtc_data_channel_h)channel, WEBRTC_DATA_CHANNEL_TYPE_STRING, string, channel->message_cb.user_data); + LOG_DEBUG("<<< end of the callback"); + } +} + +static void __data_channel_on_message_data_cb(GObject *data_channel, GBytes *data, gpointer user_data) +{ + webrtc_data_channel_s *channel = (webrtc_data_channel_s *)user_data; + + RET_IF(channel == NULL, "channel is NULL"); + + LOG_DEBUG("data_channel[%s] data[%p, size:%u] user_data[%p]", GST_OBJECT_NAME(data_channel), data, g_bytes_get_size(data), user_data); + + if (channel->message_cb.callback) { + LOG_DEBUG(">>> callback[%p] user_data[%p]", channel->message_cb.callback, channel->message_cb.user_data); + ((webrtc_data_channel_message_cb)(channel->message_cb.callback))((webrtc_data_channel_h)channel, WEBRTC_DATA_CHANNEL_TYPE_BYTES, data, channel->message_cb.user_data); + LOG_DEBUG("<<< end of the callback"); + } +} + +static void __data_channel_on_error_cb(GObject *data_channel, GError *error, gpointer user_data) +{ + webrtc_data_channel_s *channel = (webrtc_data_channel_s *)user_data; + int err = WEBRTC_ERROR_INVALID_OPERATION; + + RET_IF(channel == NULL, "channel is NULL"); + + if (error->domain == GST_RESOURCE_ERROR) + err = WEBRTC_ERROR_RESOURCE_FAILED; + else if (error->domain == GST_STREAM_ERROR) + err = WEBRTC_ERROR_STREAM_FAILED; + + LOG_DEBUG("data_channel[%s] error[0x%x] user_data[%p]", GST_OBJECT_NAME(data_channel), err, user_data); + + if (channel->error_cb.callback) { + LOG_DEBUG(">>> callback[%p] user_data[%p]", channel->error_cb.callback, channel->error_cb.user_data); + ((webrtc_data_channel_error_cb)(channel->error_cb.callback))((webrtc_data_channel_h)channel, err, channel->error_cb.user_data); + LOG_DEBUG("<<< end of the callback"); + } +} + +static void __data_channel_on_close_cb(GObject *data_channel, gpointer user_data) +{ + webrtc_data_channel_s *channel = (webrtc_data_channel_s *)user_data; + + RET_IF(channel == NULL, "channel is NULL"); + + LOG_DEBUG("data_channel[%s] user_data[%p]", GST_OBJECT_NAME(data_channel), user_data); + + if (channel->close_cb.callback) { + LOG_DEBUG(">>> callback[%p] user_data[%p]", channel->close_cb.callback, channel->close_cb.user_data); + ((webrtc_data_channel_close_cb)(channel->close_cb.callback))((webrtc_data_channel_h)channel, channel->close_cb.user_data); + LOG_DEBUG("<<< end of the callback"); + } +} + +static void __invoke_data_channel_cb(webrtc_s *webrtc, webrtc_data_channel_s *channel) +{ + RET_IF(webrtc == NULL, "webrtc is NULL"); + RET_IF(channel == NULL, "channel is NULL"); + + LOG_INFO("data_channel[%s]", GST_OBJECT_NAME(channel->channel)); + + if (webrtc->data_channel_cb.callback) { + LOG_DEBUG(">>> callback[%p] user_data[%p]", webrtc->data_channel_cb.callback, webrtc->data_channel_cb.user_data); + ((webrtc_data_channel_cb)(webrtc->data_channel_cb.callback))((webrtc_h)webrtc, channel, webrtc->data_channel_cb.user_data); + LOG_DEBUG("<<< end of the callback"); + } +} + +void _webrtcbin_on_data_channel_cb(GstElement *webrtcbin, GObject *data_channel, gpointer user_data) +{ + webrtc_s *webrtc = (webrtc_s *)user_data; + webrtc_data_channel_s *channel; + + RET_IF(webrtc == NULL, "webrtc is NULL"); + + LOG_DEBUG("data_channel[%s] user_data[%p]", GST_OBJECT_NAME(data_channel), user_data); + + channel = g_new0(webrtc_data_channel_s, 1); + g_mutex_init(&channel->mutex); + g_mutex_lock(&channel->mutex); + + channel->channel = data_channel; + + g_hash_table_insert(webrtc->data_channels, (gpointer)channel, (gpointer)channel); + + _connect_and_append_signal(&channel->signals, channel->channel, "on-open", G_CALLBACK(__data_channel_on_open_cb), channel); + _connect_and_append_signal(&channel->signals, channel->channel, "on-message-string", G_CALLBACK(__data_channel_on_message_string_cb), channel); + _connect_and_append_signal(&channel->signals, channel->channel, "on-message-data", G_CALLBACK(__data_channel_on_message_data_cb), channel); + _connect_and_append_signal(&channel->signals, channel->channel, "on-error", G_CALLBACK(__data_channel_on_error_cb), channel); + _connect_and_append_signal(&channel->signals, channel->channel, "on-close", G_CALLBACK(__data_channel_on_close_cb), channel); + + g_mutex_unlock(&channel->mutex); + + g_mutex_lock(&webrtc->mutex); + if (webrtc->state == WEBRTC_STATE_NEGOTIATING) { + webrtc_state_e old_state = webrtc->state; + webrtc->state = webrtc->pend_state = WEBRTC_STATE_PLAYING; + g_mutex_unlock(&webrtc->mutex); + _invoke_state_changed_cb(webrtc, old_state, webrtc->state); + } else { + g_mutex_unlock(&webrtc->mutex); + } + __invoke_data_channel_cb(webrtc, channel); +} + +int _create_data_channel(webrtc_s *webrtc, const char *label, webrtc_data_channel_s **channel) +{ + webrtc_data_channel_s *_channel; + GObject *data_channel; + + RET_VAL_IF(webrtc == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "webrtc is NULL"); + RET_VAL_IF(label == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "label is NULL"); + RET_VAL_IF(channel == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "channel is NULL"); + + g_signal_emit_by_name(webrtc->gst.webrtcbin, "create-data-channel", label, NULL, &data_channel); + if (!data_channel) { + LOG_ERROR("failed to create data channel"); + return WEBRTC_ERROR_INVALID_OPERATION; + } + + LOG_INFO("data channel[%s] is created", GST_OBJECT_NAME(data_channel)); + + _channel = g_new0(webrtc_data_channel_s, 1); + g_mutex_init(&_channel->mutex); + g_mutex_lock(&_channel->mutex); + + _channel->channel = data_channel; + + _connect_and_append_signal(&_channel->signals, data_channel, "on-open", G_CALLBACK(__data_channel_on_open_cb), _channel); + _connect_and_append_signal(&_channel->signals, data_channel, "on-message-string", G_CALLBACK(__data_channel_on_message_string_cb), _channel); + _connect_and_append_signal(&_channel->signals, data_channel, "on-message-data", G_CALLBACK(__data_channel_on_message_data_cb), _channel); + _connect_and_append_signal(&_channel->signals, data_channel, "on-error", G_CALLBACK(__data_channel_on_error_cb), _channel); + _connect_and_append_signal(&_channel->signals, data_channel, "on-close", G_CALLBACK(__data_channel_on_close_cb), _channel); + + *channel = _channel; + + g_mutex_unlock(&_channel->mutex); + + return WEBRTC_ERROR_NONE; +} + +int _destroy_data_channel(webrtc_data_channel_s *channel) +{ + RET_VAL_IF(channel == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "channel is NULL"); + + g_mutex_lock(&channel->mutex); + + channel->open_cb.callback = NULL; + channel->message_cb.callback = NULL; + channel->error_cb.callback = NULL; + channel->close_cb.callback = NULL; + + g_list_free_full(channel->signals, _disconnect_signal); + channel->signals = NULL; + + LOG_INFO("data_channel[%s] is destroyed", GST_OBJECT_NAME(channel->channel)); + + g_object_unref(channel->channel); + + g_mutex_unlock(&channel->mutex); + g_mutex_clear(&channel->mutex); + + g_free(channel); + + return WEBRTC_ERROR_NONE; +} + +static void __data_channel_destroy_cb(gpointer data) +{ + webrtc_data_channel_s *data_channel = (webrtc_data_channel_s *)data; + + RET_IF(data_channel == NULL, "data_channel is NULL"); + + _destroy_data_channel(data_channel); +} + +void _init_data_channels(webrtc_s *webrtc) +{ + RET_IF(webrtc == NULL, "webrtc is NULL"); + RET_IF(webrtc->data_channels, "data_channels is not NULL"); + + webrtc->data_channels = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, __data_channel_destroy_cb); +} + +void _destroy_data_channels(webrtc_s *webrtc) +{ + RET_IF(webrtc == NULL, "webrtc is NULL"); + RET_IF(webrtc->data_channels == NULL, "data_channels is NULL"); + + g_hash_table_destroy(webrtc->data_channels); + webrtc->data_channels = NULL; +} + +int _data_channel_send_string(webrtc_data_channel_s *channel, const char *string) +{ + GstWebRTCDataChannelState state; + + RET_VAL_IF(channel == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "channel is NULL"); + RET_VAL_IF(string == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "string is NULL"); + RET_VAL_IF(channel->channel == NULL, WEBRTC_ERROR_INVALID_OPERATION, "data_channel is NULL"); + + g_object_get(channel->channel, "ready-state", &state, NULL); + + RET_VAL_IF(state != GST_WEBRTC_DATA_CHANNEL_STATE_OPEN, WEBRTC_ERROR_INVALID_STATE, "channel is not opened"); + + g_signal_emit_by_name(channel->channel, "send-string", string); + + LOG_INFO("data_channel[%s] >>>>> [%s]", GST_OBJECT_NAME(channel->channel), string); + + return WEBRTC_ERROR_NONE; +} diff --git a/src/webrtc_private.c b/src/webrtc_private.c index 410cd3c1..5bdcd321 100644 --- a/src/webrtc_private.c +++ b/src/webrtc_private.c @@ -147,7 +147,7 @@ static gboolean __meet_gst_state(webrtc_state_e state, GstState gst_state) return FALSE; } -static void __invoke_state_changed_cb(webrtc_s *webrtc, webrtc_state_e old, webrtc_state_e new) +void _invoke_state_changed_cb(webrtc_s *webrtc, webrtc_state_e old, webrtc_state_e new) { RET_IF(webrtc == NULL, "webrtc is NULL"); @@ -234,7 +234,7 @@ static gboolean __bus_watch_cb(GstBus *bus, GstMessage *message, gpointer user_d webrtc_state_e old_state = webrtc->state; webrtc->state = webrtc->pend_state; g_mutex_unlock(&webrtc->mutex); - __invoke_state_changed_cb(webrtc, old_state, webrtc->state); + _invoke_state_changed_cb(webrtc, old_state, webrtc->state); break; } @@ -271,7 +271,7 @@ static gboolean __idle_cb(gpointer user_data) g_mutex_unlock(&webrtc->mutex); - __invoke_state_changed_cb(webrtc, old_state, webrtc->state); + _invoke_state_changed_cb(webrtc, old_state, webrtc->state); return G_SOURCE_REMOVE; } @@ -443,7 +443,7 @@ int _gst_init(webrtc_s *webrtc) return WEBRTC_ERROR_NONE; } -static void __connect_and_append_signal(GList **signals, GstElement *obj, const char *sig_name, GCallback cb, gpointer user_data) +void _connect_and_append_signal(GList **signals, GObject *obj, const char *sig_name, GCallback cb, gpointer user_data) { webrtc_signal_s *sig_data; @@ -453,7 +453,7 @@ static void __connect_and_append_signal(GList **signals, GstElement *obj, const RET_IF(cb == NULL, "cb is NULL"); sig_data = (webrtc_signal_s *)g_malloc0(sizeof(webrtc_signal_s)); - sig_data->obj = G_OBJECT(obj); + sig_data->obj = obj; sig_data->signal_id = g_signal_connect(sig_data->obj, sig_name, cb, user_data); if (sig_data->signal_id == 0) { LOG_ERROR("failed to g_signal_connect(), [%s] for object [%s]", sig_name, GST_OBJECT_NAME(obj)); @@ -462,20 +462,18 @@ static void __connect_and_append_signal(GList **signals, GstElement *obj, const } *signals = g_list_append(*signals, sig_data); - LOG_DEBUG("signal [%s] with id[%lu] is connected to object [%s].", sig_name, sig_data->signal_id, GST_OBJECT_NAME(sig_data->obj)); + LOG_DEBUG("signal [%s] with id[%lu] is connected to object [%s]", sig_name, sig_data->signal_id, GST_OBJECT_NAME(sig_data->obj)); } -static void __disconnect_signal(gpointer data) +void _disconnect_signal(gpointer data) { webrtc_signal_s *sig_data = (webrtc_signal_s *)data; RET_IF(sig_data == NULL, "sig_data is NULL"); - if (GST_IS_ELEMENT(sig_data->obj)) { - if (g_signal_handler_is_connected(sig_data->obj, sig_data->signal_id)) { - g_signal_handler_disconnect(sig_data->obj, sig_data->signal_id); - LOG_DEBUG("signal with id[%lu] is disconnected from object [%s]", sig_data->signal_id, GST_OBJECT_NAME(sig_data->obj)); - } + if (g_signal_handler_is_connected(sig_data->obj, sig_data->signal_id)) { + g_signal_handler_disconnect(sig_data->obj, sig_data->signal_id); + LOG_DEBUG("signal with id[%lu] is disconnected from object [%s]", sig_data->signal_id, GST_OBJECT_NAME(sig_data->obj)); } g_free(sig_data); @@ -546,7 +544,7 @@ static void __webrtcbin_peer_connection_state_cb(GstElement *webrtcbin, GParamSp webrtc_state_e old_state = webrtc->state; webrtc->state = webrtc->pend_state = WEBRTC_STATE_PLAYING; g_mutex_unlock(&webrtc->mutex); - __invoke_state_changed_cb(webrtc, old_state, webrtc->state); + _invoke_state_changed_cb(webrtc, old_state, webrtc->state); break; } @@ -846,15 +844,16 @@ 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_cb), webrtc); - __connect_and_append_signal(&webrtc->signals, webrtc->gst.webrtcbin, "on-ice-candidate", G_CALLBACK(__webrtcbin_on_ice_candidate_cb), webrtc); - __connect_and_append_signal(&webrtc->signals, webrtc->gst.webrtcbin, "notify::connection-state", G_CALLBACK(__webrtcbin_peer_connection_state_cb), webrtc); - __connect_and_append_signal(&webrtc->signals, webrtc->gst.webrtcbin, "notify::signaling-state", G_CALLBACK(__webrtcbin_signaling_state_cb), webrtc); - __connect_and_append_signal(&webrtc->signals, webrtc->gst.webrtcbin, "notify::ice-gathering-state", G_CALLBACK(__webrtcbin_ice_gathering_state_cb), webrtc); - __connect_and_append_signal(&webrtc->signals, webrtc->gst.webrtcbin, "notify::ice-connection-state", G_CALLBACK(__webrtcbin_ice_connection_state_cb), webrtc); - __connect_and_append_signal(&webrtc->signals, webrtc->gst.webrtcbin, "pad-added", G_CALLBACK(__webrtcbin_pad_added_cb), webrtc); - __connect_and_append_signal(&webrtc->signals, webrtc->gst.webrtcbin, "no-more-pads", G_CALLBACK(__webrtcbin_no_more_pads_cb), webrtc); - __connect_and_append_signal(&webrtc->signals, webrtc->gst.webrtcbin, "on-new-transceiver", G_CALLBACK(__webrtcbin_on_new_transceiver_cb), webrtc); + _connect_and_append_signal(&webrtc->signals, (GObject *)webrtc->gst.webrtcbin, "on-negotiation-needed", G_CALLBACK(__webrtcbin_on_negotiation_needed_cb), webrtc); + _connect_and_append_signal(&webrtc->signals, (GObject *)webrtc->gst.webrtcbin, "on-ice-candidate", G_CALLBACK(__webrtcbin_on_ice_candidate_cb), webrtc); + _connect_and_append_signal(&webrtc->signals, (GObject *)webrtc->gst.webrtcbin, "notify::connection-state", G_CALLBACK(__webrtcbin_peer_connection_state_cb), webrtc); + _connect_and_append_signal(&webrtc->signals, (GObject *)webrtc->gst.webrtcbin, "notify::signaling-state", G_CALLBACK(__webrtcbin_signaling_state_cb), webrtc); + _connect_and_append_signal(&webrtc->signals, (GObject *)webrtc->gst.webrtcbin, "notify::ice-gathering-state", G_CALLBACK(__webrtcbin_ice_gathering_state_cb), webrtc); + _connect_and_append_signal(&webrtc->signals, (GObject *)webrtc->gst.webrtcbin, "notify::ice-connection-state", G_CALLBACK(__webrtcbin_ice_connection_state_cb), webrtc); + _connect_and_append_signal(&webrtc->signals, (GObject *)webrtc->gst.webrtcbin, "pad-added", G_CALLBACK(__webrtcbin_pad_added_cb), webrtc); + _connect_and_append_signal(&webrtc->signals, (GObject *)webrtc->gst.webrtcbin, "no-more-pads", G_CALLBACK(__webrtcbin_no_more_pads_cb), webrtc); + _connect_and_append_signal(&webrtc->signals, (GObject *)webrtc->gst.webrtcbin, "on-new-transceiver", G_CALLBACK(__webrtcbin_on_new_transceiver_cb), webrtc); + _connect_and_append_signal(&webrtc->signals, (GObject *)webrtc->gst.webrtcbin, "on-data-channel", G_CALLBACK(_webrtcbin_on_data_channel_cb), 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)); @@ -893,7 +892,7 @@ void _gst_destroy_pipeline(webrtc_s *webrtc) webrtc->gst.bus = NULL; } if (webrtc->signals) { - g_list_free_full(webrtc->signals, __disconnect_signal); + g_list_free_full(webrtc->signals, _disconnect_signal); webrtc->signals = NULL; } if (webrtc->gst.webrtcbin) { diff --git a/test/webrtc_test.c b/test/webrtc_test.c index f5b1f4aa..b914ed01 100644 --- a/test/webrtc_test.c +++ b/test/webrtc_test.c @@ -32,6 +32,7 @@ #define LOG_TAG "WEBRTC_TEST" #define MAX_STRING_LEN 512 +#define MAX_CHANNEL_LEN 10 enum { CURRENT_STATUS_MAINMENU, @@ -39,6 +40,7 @@ enum { CURRENT_STATUS_REMOVE_MEDIA_SOURCE, CURRENT_STATUS_GET_TRANSCEIVER_DIRECTION, CURRENT_STATUS_SET_TRANSCEIVER_DIRECTION, + CURRENT_STATUS_DATA_CHANNEL_SEND_STRING, CURRENT_STATUS_SET_STUN_SERVER, CURRENT_STATUS_SET_LOCAL_DESCRIPTION, CURRENT_STATUS_SET_REMOTE_DESCRIPTION, @@ -91,6 +93,9 @@ typedef struct { static appdata ad; static webrtc_h g_webrtc; +static webrtc_data_channel_h g_channels[MAX_CHANNEL_LEN]; +static int g_channel_index; +static webrtc_data_channel_h g_recv_channels[MAX_CHANNEL_LEN]; static char *g_offer; static char *g_answer; static char *g_remote_desc; @@ -238,6 +243,7 @@ static void _webrtc_create() static void _webrtc_destroy() { int ret = WEBRTC_ERROR_NONE; + int i; if (!g_webrtc) { g_print("failed to _webrtc_destroy(), g_webrtc is NULL\n"); @@ -250,6 +256,13 @@ static void _webrtc_destroy() } else { g_print("webrtc_destroy() success\n"); g_webrtc = NULL; + for (i = 0; i < MAX_CHANNEL_LEN; i++) { + if (g_channels[i] != NULL) + g_channels[i] = NULL; + if (g_recv_channels[i] != NULL) + g_recv_channels[i] = NULL; + } + g_channel_index = 0; } } @@ -272,6 +285,7 @@ static void _webrtc_start() static void _webrtc_stop() { int ret = WEBRTC_ERROR_NONE; + int i; if (!g_webrtc) { g_print("failed to _webrtc_stop(), g_webrtc is NULL\n"); @@ -279,10 +293,15 @@ static void _webrtc_stop() } ret = webrtc_stop(g_webrtc); - if (ret != WEBRTC_ERROR_NONE) + if (ret != WEBRTC_ERROR_NONE) { g_print("webrtc_stop() returned [0x%x]\n", ret); - else + } else { g_print("webrtc_stop() success\n"); + for (i = 0; i < MAX_CHANNEL_LEN; i++) { + if (g_recv_channels[i] != NULL) + g_recv_channels[i] = NULL; + } + } } static void _webrtc_get_state() @@ -364,6 +383,34 @@ static int __copy_string_arr(gchar *dest_arr, char *string) return 0; } +static void _webrtc_data_channel_send_string(const char *string) +{ + int ret = WEBRTC_ERROR_NONE; + int i; + + for (i = 0; i < MAX_CHANNEL_LEN; i++) { + if (g_channels[i] == NULL) + continue; + g_print("data_channel[%p], index[%d]: ", g_channels[i], i); + ret = webrtc_data_channel_send_string(g_channels[i], string); + if (ret != WEBRTC_ERROR_NONE) + g_print("failed to webrtc_data_channel_send_string(), string[%s]\n", string); + else + g_print("webrtc_data_channel_send_string() success, string[%s]\n", string); + } + + for (i = 0; i < MAX_CHANNEL_LEN; i++) { + if (g_recv_channels[i] == NULL) + continue; + g_print("received data_channel[%p], index[%d]: ", g_recv_channels[i], i); + ret = webrtc_data_channel_send_string(g_recv_channels[i], string); + if (ret != WEBRTC_ERROR_NONE) + g_print("failed to webrtc_data_channel_send_string(), string[%s]\n", string); + else + g_print("webrtc_data_channel_send_string() success, string[%s]\n", string); + } +} + static void _webrtc_set_stun_server(char *uri) { int ret = 0; @@ -382,6 +429,71 @@ static void _webrtc_set_stun_server(char *uri) g_print("webrtc_set_stun_server() success, uri[%s]\n", g_stun_server); } +static void __data_channel_open_cb(webrtc_data_channel_h channel, void *user_data) +{ + g_print("__data_channel_open_cb() is called, channel[%p]\n", channel); +} + +static void __data_channel_message_cb(webrtc_data_channel_h channel, webrtc_data_channel_type_e type, void *message, void *user_data) +{ + g_print("__data_channel_message_cb() is called, channel[%p], type[%d], ", channel, type); + if (type == WEBRTC_DATA_CHANNEL_TYPE_STRING) + g_print("string message[%s]\n", (char *)message); + else if (type == WEBRTC_DATA_CHANNEL_TYPE_BYTES) + g_print("bytes message[%p, size:%u]\n", message, g_bytes_get_size(message)); +} + +static void __data_channel_error_cb(webrtc_data_channel_h channel, webrtc_error_e error, void *user_data) +{ + g_print("__data_channel_error_cb() is called, channel[%p]\n", channel); +} + +static void __data_channel_close_cb(webrtc_data_channel_h channel, void *user_data) +{ + g_print("__data_channel_close_cb() is called, channel[%p]\n", channel); +} + +static void __data_channel_cb(webrtc_h webrtc, webrtc_data_channel_h channel, void *user_data) +{ + int i; + + for (i = 0; i < MAX_CHANNEL_LEN; i++) { + if (g_recv_channels[i] == NULL) { + g_print("__data_channel_cb() is called, append all the callbacks.\n"); + + g_recv_channels[i] = channel; + webrtc_data_channel_set_open_cb(channel, __data_channel_open_cb, webrtc); + webrtc_data_channel_set_message_cb(channel, __data_channel_message_cb, webrtc); + webrtc_data_channel_set_error_cb(channel, __data_channel_error_cb, webrtc); + webrtc_data_channel_set_close_cb(channel, __data_channel_close_cb, webrtc); + return; + } + } + g_print("__data_channel_cb() is called, but g_recv_channels is full\n"); +} + +static void _webrtc_set_data_channel_cb() +{ + int ret = WEBRTC_ERROR_NONE; + + ret = webrtc_set_data_channel_cb(g_webrtc, __data_channel_cb, g_webrtc); + if (ret != WEBRTC_ERROR_NONE) + g_print("failed to webrtc_set_data_channel_cb()\n"); + else + g_print("webrtc_set_data_channel_cb() success\n"); +} + +static void _webrtc_unset_data_channel_cb() +{ + int ret = WEBRTC_ERROR_NONE; + + ret = webrtc_unset_data_channel_cb(g_webrtc); + if (ret != WEBRTC_ERROR_NONE) + g_print("failed to webrtc_unset_data_channel_cb()\n"); + else + g_print("webrtc_unset_data_channel_cb() success\n"); +} + static void __error_cb(webrtc_h webrtc, webrtc_error_e error, webrtc_state_e state, void *user_data) { g_print("__error_cb() is invoked, webrtc[%p], error[0x%x], state[%d], user_data[%p]\n", @@ -565,6 +677,54 @@ 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); + + g_ice_candidates = NULL; +} + +static void _webrtc_create_data_channel(void) +{ + int ret = WEBRTC_ERROR_NONE; + gchar *label; + + if (g_channel_index == MAX_CHANNEL_LEN) { + g_print("%d channels are already created, skip it\n", MAX_CHANNEL_LEN); + return; + } + + label = g_strdup_printf("data_channel_%d", g_channel_index); + + ret = webrtc_create_data_channel(g_webrtc, label, &g_channels[g_channel_index]); + if (ret != WEBRTC_ERROR_NONE) { + g_print("failed to webrtc_create_data_channel()\n"); + } else { + g_print("webrtc_create_data_channel() success, channel[%p], index[%d]\n", g_channels[g_channel_index], g_channel_index); + webrtc_data_channel_set_open_cb(g_channels[g_channel_index], __data_channel_open_cb, g_webrtc); + webrtc_data_channel_set_message_cb(g_channels[g_channel_index], __data_channel_message_cb, g_webrtc); + webrtc_data_channel_set_error_cb(g_channels[g_channel_index], __data_channel_error_cb, g_webrtc); + webrtc_data_channel_set_close_cb(g_channels[g_channel_index], __data_channel_close_cb, g_webrtc); + g_channel_index++; + } + + g_free(label); +} + +static void _webrtc_destroy_data_channel(void) +{ + int ret = WEBRTC_ERROR_NONE; + int i; + + for (i = 0; i < MAX_CHANNEL_LEN; i++) { + if (g_channels[i] == NULL) + continue; + ret = webrtc_destroy_data_channel(g_channels[i]); + if (ret != WEBRTC_ERROR_NONE) { + g_print("failed to _webrtc_destroy_data_channel(), index[%d]\n", i); + } else { + g_print("_webrtc_destroy_data_channel() success, index[%d]\n", i); + g_channels[i] = NULL; + } + } + g_channel_index = 0; } static void _setting_uri(gchar *dest_arr, char *uri) @@ -839,6 +999,21 @@ void _interpret_main_menu(char *cmd) } else if (strncmp(cmd, "td", 2) == 0) { g_menu_state = CURRENT_STATUS_SET_TRANSCEIVER_DIRECTION; + } else if (strncmp(cmd, "cd", 2) == 0) { + _webrtc_create_data_channel(); + + } else if (strncmp(cmd, "dd", 2) == 0) { + _webrtc_destroy_data_channel(); + + } else if (strncmp(cmd, "sz", 2) == 0) { + _webrtc_set_data_channel_cb(); + + } else if (strncmp(cmd, "uz", 2) == 0) { + _webrtc_unset_data_channel_cb(); + + } else if (strncmp(cmd, "zs", 2) == 0) { + g_menu_state = CURRENT_STATUS_DATA_CHANNEL_SEND_STRING; + } else if (strncmp(cmd, "se", 2) == 0) { _webrtc_set_error_cb(); @@ -958,6 +1133,10 @@ void display_sub_basic() g_print("r. Remove media source\n"); g_print("gd. Get transceiver direction\t"); g_print("td. Set transceiver direction\n"); + g_print("cd. Create data channel\t"); + g_print("dd. Destroy data channel\n"); + g_print("zs. Send string via data channel\n"); + g_print("------------------------------------- Callbacks -----------------------------------------\n"); g_print("se. Set error callback\t"); g_print("ue. Unset error callback\n"); g_print("sc. Set state changed callback\t"); @@ -966,13 +1145,16 @@ void display_sub_basic() 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("sz. Set data channel callback\t"); + g_print("uz. Unset data channel callback\n"); + g_print("------------------------------------- Negotiation ---------------------------------------\n"); + g_print("st. Set STUN server\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"); + g_print("------------------------------------- App. Setting --------------------------------------\n"); display_setting_status(); g_print("px. Set proxy URL\n"); g_print("ss. Set signaling server URL\n"); @@ -1008,6 +1190,9 @@ static void displaymenu() else if (g_cnt == 2) g_print("*** input transceiver direction.(1:sendonly 2:recvonly, 3:sendrecv)\n"); + } else if (g_menu_state == CURRENT_STATUS_DATA_CHANNEL_SEND_STRING) { + g_print("*** input string to send.\n"); + } else if (g_menu_state == CURRENT_STATUS_SET_STUN_SERVER) { g_print("*** input STUN server address.\n"); @@ -1116,6 +1301,11 @@ static void interpret(char *cmd) } break; } + case CURRENT_STATUS_DATA_CHANNEL_SEND_STRING: { + _webrtc_data_channel_send_string(cmd); + reset_menu_state(); + break; + } case CURRENT_STATUS_SET_STUN_SERVER: { _webrtc_set_stun_server(cmd); reset_menu_state();