webrtc_test: Add menu to join a room 47/262247/4
authorSangchul Lee <sc11.lee@samsung.com>
Mon, 21 Jun 2021 11:47:25 +0000 (20:47 +0900)
committerSangchul Lee <sc11.lee@samsung.com>
Thu, 5 Aug 2021 10:00:14 +0000 (19:00 +0900)
New menu is added as below.
 rj. Request join room

In case of this test, all of the webrtc configuration including
sources and negotiation will be make up automatically.

Test sequence example
 1. 'ss' -> set signaling server
 2. 'cs' -> connect to the server
 3. 'rj' -> 1 or 2 (choose source type) -> type room name
(It is required to do the same for other handles)

[Version] 0.2.66
[Issue Type] New feature

Change-Id: I62841efc8ad477bdc6968e258bed3dae1ee90400
Signed-off-by: Sangchul Lee <sc11.lee@samsung.com>
packaging/capi-media-webrtc.spec
test/webrtc_test.c

index e25007a639170804420636acc76cd924e60057d0..15fd161c879e97cd6de123651ed65a54e73095a9 100644 (file)
@@ -1,6 +1,6 @@
 Name:       capi-media-webrtc
 Summary:    A WebRTC library in Tizen Native API
-Version:    0.2.65
+Version:    0.2.66
 Release:    0
 Group:      Multimedia/API
 License:    Apache-2.0
index f93cc9d6654cdbc9416334e2caa4ec8ffe5c0b9a..9031179929725ccb5db4260cd8773fefc5648105 100644 (file)
@@ -86,6 +86,7 @@ enum {
        CURRENT_STATUS_SETTING_PROXY,
        CURRENT_STATUS_CHANGE_CONNECTION,
        CURRENT_STATUS_REQUEST_SESSION,
+       CURRENT_STATUS_REQUEST_JOIN_ROOM,
        CURRENT_STATUS_SEND_LOCAL_DESCRIPTION,
        CURRENT_STATUS_START_PUSHING_PACKET_TO_MEDIA_PACKET_SOURCE,
        CURRENT_STATUS_STOP_PUSHING_PACKET_TO_MEDIA_PACKET_SOURCE,
@@ -100,6 +101,7 @@ enum {
        SERVER_STATUS_DISCONNECTED,
        SERVER_STATUS_CONNECTED,
        SERVER_STATUS_SESSION_ESTABLISHED,
+       SERVER_STATUS_ROOM_ESTABLISHED,
        SERVER_STATUS_ERROR_FOUND
 };
 
@@ -107,6 +109,7 @@ const char *g_server_status_str[] = {
        "DISCONNECTED",
        "CONNECTED",
        "SESSION_ESTABLISHED",
+       "ROOM_ESTABLISHED",
        "ERROR_FOUND",
 };
 
@@ -164,8 +167,13 @@ typedef struct {
 typedef struct _connection_s {
        SoupWebsocketConnection *ws_conn;
        gint32 local_peer_id;
+       int remote_peer_id;
        int server_status;
 
+       bool is_for_room;
+       bool is_offer;
+       int room_source_type;
+
        /* for private network */
        webrtc_signaling_client_h signaling_client;
        bool is_connected;
@@ -227,7 +235,10 @@ static Evas_Object *create_win(const char *name, int *w, int *h)
 {
        Evas_Object *eo = NULL;
 
-       g_print("[%s][%d] name=%s\n", __func__, __LINE__, name);
+       if (!w || !h) {
+               g_printerr("w[%p] or h[%p] is NULL\n", w, h);
+               return NULL;
+       }
 
        eo = elm_win_add(NULL, name, ELM_WIN_BASIC);
        if (eo) {
@@ -235,11 +246,12 @@ static Evas_Object *create_win(const char *name, int *w, int *h)
                elm_win_borderless_set(eo, EINA_TRUE);
                evas_object_smart_callback_add(eo, "delete,request", win_del, NULL);
                elm_win_screen_size_get(eo, NULL, NULL, w, h);
-               g_print("window size: %d x %d\n", *w, *h);
+               g_print("eo[%p], window size: %d x %d\n", eo, *w, *h);
                evas_object_resize(eo, *w, *h);
                elm_win_autodel_set(eo, EINA_TRUE);
                elm_win_alpha_set(eo, EINA_TRUE);
        }
+
        return eo;
 }
 
@@ -254,7 +266,7 @@ static Evas_Object *create_image_object(Evas_Object *eo_parent)
        evas = evas_object_evas_get(eo_parent);
        eo = evas_object_image_add(evas);
 
-       g_print("eo [%p]\n", eo);
+       g_print("eo[%p]\n", eo);
 
        return eo;
 }
@@ -308,6 +320,10 @@ static int app_create(void *data)
        for (i = 0; i < MAX_CONNECTION_LEN + 1; i++) {
                eo = (i == 0) ? &g_eo_mine : &g_conns[i - 1].render.eo;
                *eo = create_image_object(ad->win);
+               if (!*eo) {
+                       g_print("failed to create evas image object\n");
+                       continue;
+               }
                evas_object_image_size_set(*eo, ad->win_width / 2, ad->win_height / 2);
                evas_object_image_fill_set(*eo, 0, 0, ad->win_width / 2, ad->win_height / 2);
                evas_object_resize(*eo, ad->win_width / 2, ad->win_height / 2);
@@ -587,10 +603,10 @@ static int __get_sound_stream_info(sound_stream_info_h *stream_info)
        return 0;
 }
 
-static void _webrtc_add_media_source(int index, int value)
+static void _webrtc_add_media_source(int index, int value, unsigned int *source_id)
 {
        int ret = WEBRTC_ERROR_NONE;
-       unsigned int source_id = 0;
+       unsigned int _source_id = 0;
        int i;
 
        if (value - 1 <= WEBRTC_MEDIA_SOURCE_TYPE_MEDIA_PACKET) {
@@ -601,7 +617,7 @@ static void _webrtc_add_media_source(int index, int value)
                                i = _get_empty_packet_sources_index(index);
                                RET_IF(i == -1, "media packet source can be added up to %d", MAX_MEDIA_PACKET_SOURCE_LEN);
                        }
-                       ret = webrtc_add_media_source(g_conns[index].webrtc, type, &source_id);
+                       ret = webrtc_add_media_source(g_conns[index].webrtc, type, &_source_id);
                        RET_IF(ret != WEBRTC_ERROR_NONE, "ret[0x%x]", ret);
 
                        if (type == WEBRTC_MEDIA_SOURCE_TYPE_MIC) {
@@ -614,7 +630,7 @@ static void _webrtc_add_media_source(int index, int value)
                                        g_printerr("failed to __get_sound_stream_info()\n");
 
                                } else {
-                                       ret = webrtc_mic_source_set_sound_stream_info(g_conns[index].webrtc, source_id, g_conns[index].source.stream_info);
+                                       ret = webrtc_mic_source_set_sound_stream_info(g_conns[index].webrtc, _source_id, g_conns[index].source.stream_info);
                                        if (ret != WEBRTC_ERROR_NONE)
                                                g_printerr("failed to webrtc_mic_source_set_sound_stream_info(), ret[0x%x]\n", ret);
                                }
@@ -625,7 +641,7 @@ static void _webrtc_add_media_source(int index, int value)
                        return;
                }
                if (type == WEBRTC_MEDIA_SOURCE_TYPE_MEDIA_PACKET) {
-                       g_conns[index].packet_sources[i].source_id = source_id;
+                       g_conns[index].packet_sources[i].source_id = _source_id;
                        g_mutex_init(&g_conns[index].packet_sources[i].mutex);
                        g_cond_init(&g_conns[index].packet_sources[i].cond);
                }
@@ -635,7 +651,7 @@ static void _webrtc_add_media_source(int index, int value)
                switch (type) {
                case WEBRTC_MEDIA_SOURCE_TYPE_CUSTOM_AUDIO:
                case WEBRTC_MEDIA_SOURCE_TYPE_CUSTOM_VIDEO:
-                       ret = webrtc_add_media_source_internal(g_conns[index].webrtc, type, &source_id);
+                       ret = webrtc_add_media_source_internal(g_conns[index].webrtc, type, &_source_id);
                        RET_IF(ret != WEBRTC_ERROR_NONE, "ret[0x%x]", ret);
                        break;
                default:
@@ -644,7 +660,9 @@ static void _webrtc_add_media_source(int index, int value)
                }
        }
 
-       g_print("_webrtc_add_media_source() success, source_id[%u]\n", source_id);
+       g_print("_webrtc_add_media_source() success, source_id[%u]\n", _source_id);
+       if (source_id)
+               *source_id = _source_id;
 }
 
 static void _webrtc_remove_media_source(int index, unsigned int source_id)
@@ -705,8 +723,8 @@ static void _webrtc_media_source_get_video_resolution(int index, unsigned int so
        g_print("webrtc_media_source_get_video_resolution() success, source_id[%u], [%dx%d]\n", source_id, width, height);
 }
 
-#define VIDEO_WIDTH       352
-#define VIDEO_HEIGHT      288
+#define VIDEO_WIDTH       320
+#define VIDEO_HEIGHT      240
 #define VIDEO_FRAME_RATE  30
 #define AUDIO_CHANNEL     1
 #define AUDIO_SAMPLERATE  8000
@@ -994,6 +1012,93 @@ static void _webrtc_media_source_set_mute(int index, unsigned int source_id, web
                g_print("webrtc_media_source_get_mute() success, source_id[%d], mute_status[%d]\n", source_id, mute_status);
 }
 
+static void _webrtc_create_offer(connection_s *conn)
+{
+       int ret = WEBRTC_ERROR_NONE;
+
+       RET_IF(!conn, "conn is NULL");
+
+       if (conn->offer) {
+               free(conn->offer);
+               conn->offer = NULL;
+       }
+
+       ret = webrtc_create_offer(conn->webrtc, NULL, &conn->offer);
+       RET_IF(ret != WEBRTC_ERROR_NONE, "ret[0x%x]", ret);
+
+       g_print("webrtc_create_offer() success\noffer:\n%s\n", conn->offer);
+}
+
+static void _webrtc_create_answer(connection_s *conn)
+{
+       int ret = WEBRTC_ERROR_NONE;
+
+       RET_IF(!conn, "conn is NULL");
+
+       if (conn->answer) {
+               free(conn->answer);
+               conn->answer = NULL;
+       }
+
+       ret = webrtc_create_answer(conn->webrtc, NULL, &conn->answer);
+       RET_IF(ret != WEBRTC_ERROR_NONE, "ret[0x%x]", ret);
+
+       g_print("webrtc_create_answer() success\nanswer:\n%s\n", conn->answer);
+}
+
+static void _webrtc_set_local_description(connection_s *conn, bool is_offer)
+{
+       int ret = WEBRTC_ERROR_NONE;
+
+       RET_IF(!conn, "conn is NULL");
+
+       ret = webrtc_set_local_description(conn->webrtc, is_offer ? conn->offer : conn->answer);
+       RET_IF(ret != WEBRTC_ERROR_NONE, "ret[0x%x]", ret);
+
+       g_print("webrtc_set_local_description() success\n");
+}
+
+static void _webrtc_set_remote_description(connection_s *conn)
+{
+       int ret = WEBRTC_ERROR_NONE;
+
+       RET_IF(!conn, "conn is NULL");
+
+       ret = webrtc_set_remote_description(conn->webrtc, conn->remote_desc);
+       RET_IF(ret != WEBRTC_ERROR_NONE, "ret[0x%x]", ret);
+
+       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_conns[g_conn_index].webrtc, (const char *)data);
+       RET_IF(ret != WEBRTC_ERROR_NONE, "ret[0x%x]", ret);
+
+       g_print("webrtc_ice_candidate() success\n");
+}
+
+static void _webrtc_add_ice_candidate(connection_s *conn, const gchar *candidate)
+{
+       RET_IF(!conn, "conn is NULL");
+
+       if (conn->is_for_room && candidate) {
+               int ret = webrtc_add_ice_candidate(conn->webrtc, (const char *)candidate);
+               RET_IF(ret != WEBRTC_ERROR_NONE, "ret[0x%x]", ret);
+
+               g_print("webrtc_ice_candidate() success\n");
+               return;
+       }
+
+       g_list_foreach(conn->ice_candidates, __foreach_ice_candidate, NULL);
+
+       g_list_free_full(conn->ice_candidates, free);
+
+       conn->ice_candidates = NULL;
+}
+
 static void _webrtc_set_stun_server(int index, char *uri)
 {
        int ret = 0;
@@ -1320,17 +1425,50 @@ static void _webrtc_unset_error_cb(int index)
        g_print("webrtc_unset_error_cb() success\n");
 }
 
+static void _websocket_connection_send_text_for_room(connection_s *conn, int remote_peer_id, const char *message)
+{
+       gchar *message_for_room;
+
+       RET_IF(!conn, "conn is NULL");
+       RET_IF(!conn->is_for_room, "conn[%p] is not for room", conn);
+       RET_IF(!message, "message is NULL");
+
+       message_for_room = g_strdup_printf ("ROOM_PEER_MSG %d %s", remote_peer_id, message);
+       soup_websocket_connection_send_text(conn->ws_conn, message_for_room);
+       g_free(message_for_room);
+}
+
 static void __state_changed_cb(webrtc_h webrtc, webrtc_state_e previous, webrtc_state_e current, void *user_data)
 {
-       g_print("__state_changed_cb() is invoked, webrtc[%p], prev[%s] -> curr[%s], user_data[%p]\n",
-               webrtc, g_webrtc_state_str[previous], g_webrtc_state_str[current], user_data);
+       connection_s *conn = (connection_s *)user_data;
+
+       if (!conn) {
+               g_printerr("__state_changed_cb(), conn is NULL\n");
+               return;
+       }
+
+       g_print("__state_changed_cb() is invoked, webrtc[%p], prev[%s] -> curr[%s], conn[%p]\n",
+               webrtc, g_webrtc_state_str[previous], g_webrtc_state_str[current], conn);
+
+       if (conn->is_for_room && current == WEBRTC_STATE_NEGOTIATING) {
+               if (conn->is_offer) {
+                       _webrtc_create_offer(conn);
+                       _webrtc_set_local_description(conn, true);
+                       _websocket_connection_send_text_for_room(conn, conn->remote_peer_id, conn->offer);
+               } else {
+                       if (conn->remote_desc)
+                               _webrtc_set_remote_description(conn);
+                       if (conn->ice_candidates)
+                               _webrtc_add_ice_candidate(conn, NULL);
+               }
+       }
 }
 
 static void _webrtc_set_state_changed_cb(int index)
 {
        int ret = WEBRTC_ERROR_NONE;
 
-       ret = webrtc_set_state_changed_cb(g_conns[index].webrtc, __state_changed_cb, g_conns[index].webrtc);
+       ret = webrtc_set_state_changed_cb(g_conns[index].webrtc, __state_changed_cb, &g_conns[index]);
        RET_IF(ret != WEBRTC_ERROR_NONE, "ret[0x%x]", ret);
 
        g_print("webrtc_set_state_changed_cb() success\n");
@@ -1438,11 +1576,14 @@ static void _webrtc_signaling_request_session(int index, int peer_id)
        g_print("webrtc_signaling_request_session() success\n");
 }
 
-static void _webrtc_signaling_send_message(int index, const char *message)
+static void _webrtc_signaling_send_message(connection_s *conn, const char *message)
 {
        int ret = WEBRTC_ERROR_NONE;
 
-       ret = webrtc_signaling_send_message(g_conns[index].signaling_client, message);
+       RET_IF(!conn, "conn is NULL");
+       RET_IF(!message, "message is NULL");
+
+       ret = webrtc_signaling_send_message(conn->signaling_client, message);
        RET_IF(ret != WEBRTC_ERROR_NONE, "ret[0x%x]", ret);
 
        g_print("webrtc_signaling_send_message() success\n");
@@ -1450,20 +1591,27 @@ static void _webrtc_signaling_send_message(int index, const char *message)
 
 static void __ice_candidate_cb(webrtc_h webrtc, const char *candidate, void *user_data)
 {
-       g_print("__ice_candidate_cb() is invoked\n");
+       connection_s *conn = (connection_s *)user_data;
+
+       g_print("__ice_candidate_cb() is invoked, conn[%p]\n", conn);
+       RET_IF(!conn, "conn is NULL");
 
        g_print("\n[to SERVER > ICE]\n%s\n", candidate);
-       if (g_conns[g_conn_index].ws_conn)
-               soup_websocket_connection_send_text(g_conns[g_conn_index].ws_conn, candidate);
-       else if (g_conns[g_conn_index].is_connected)
-               _webrtc_signaling_send_message(g_conn_index, candidate);
+       if (conn->ws_conn) {
+               if (conn->is_for_room)
+                       _websocket_connection_send_text_for_room(conn, conn->remote_peer_id, candidate);
+               else
+                       soup_websocket_connection_send_text(conn->ws_conn, candidate);
+       } else if (conn->signaling_client && conn->is_connected) {
+               _webrtc_signaling_send_message(conn, candidate);
+       }
 }
 
 static void _webrtc_set_ice_candidate_cb(int index)
 {
        int ret = WEBRTC_ERROR_NONE;
 
-       ret = webrtc_set_ice_candidate_cb(g_conns[index].webrtc, __ice_candidate_cb, g_conns[index].webrtc);
+       ret = webrtc_set_ice_candidate_cb(g_conns[index].webrtc, __ice_candidate_cb, &g_conns[index]);
        RET_IF(ret != WEBRTC_ERROR_NONE, "ret[0x%x]", ret);
 
        g_print("webrtc_set_ice_candidate_cb() success\n");
@@ -1484,9 +1632,32 @@ static void __peer_connection_state_change_cb(webrtc_h webrtc, webrtc_peer_conne
        g_print("__peer_connection_state_change_cb() is invoked, state[%d]\n", state);
 }
 
+static gboolean __idle_cb_for_create_answer_set_local_desc(gpointer user_data)
+{
+       connection_s *conn = (connection_s *)user_data;
+       g_print("__idle_cb_for_create_answer_set_local_desc()\n");
+       _webrtc_create_answer(conn);
+       _webrtc_set_local_description(conn, false);
+       _websocket_connection_send_text_for_room(conn, conn->remote_peer_id, conn->answer);
+
+       return G_SOURCE_REMOVE;
+}
+
 static void __signaling_state_change_cb(webrtc_h webrtc, webrtc_signaling_state_e state, void *user_data)
 {
+       connection_s *conn = (connection_s *)user_data;
+
        g_print("__signaling_state_change_cb() is invoked, state[%d]\n", state);
+
+       if (conn->is_for_room && state == WEBRTC_SIGNALING_STATE_HAVE_REMOTE_OFFER) {
+               g_idle_add(__idle_cb_for_create_answer_set_local_desc, conn);
+               /* FIXME: Commented codes can be enabled if this callback is invoked in main thread. */
+#if 0
+               _webrtc_create_answer(conn);
+               _webrtc_set_local_description(conn, false);
+               _websocket_connection_send_text_for_room(conn, conn->remote_peer_id, conn->answer);
+#endif
+       }
 }
 
 static void __ice_gathering_state_change_cb(webrtc_h webrtc, webrtc_ice_gathering_state_e state, void *user_data)
@@ -1503,25 +1674,25 @@ static void _webrtc_set_all_negotiation_state_change_cbs(int index)
 {
        int ret = WEBRTC_ERROR_NONE;
 
-       ret = webrtc_set_peer_connection_state_change_cb(g_conns[index].webrtc, __peer_connection_state_change_cb, g_conns[index].webrtc);
+       ret = webrtc_set_peer_connection_state_change_cb(g_conns[index].webrtc, __peer_connection_state_change_cb, &g_conns[index]);
        if (ret != WEBRTC_ERROR_NONE)
                g_print("failed to webrtc_set_peer_connection_state_change_cb()\n");
        else
                g_print("webrtc_set_peer_connection_state_change_cb() success\n");
 
-       ret = webrtc_set_signaling_state_change_cb(g_conns[index].webrtc, __signaling_state_change_cb, g_conns[index].webrtc);
+       ret = webrtc_set_signaling_state_change_cb(g_conns[index].webrtc, __signaling_state_change_cb, &g_conns[index]);
        if (ret != WEBRTC_ERROR_NONE)
                g_print("failed to webrtc_set_signaling_state_change_cb()\n");
        else
                g_print("webrtc_set_signaling_state_change_cb() success\n");
 
-       ret = webrtc_set_ice_gathering_state_change_cb(g_conns[index].webrtc, __ice_gathering_state_change_cb, g_conns[index].webrtc);
+       ret = webrtc_set_ice_gathering_state_change_cb(g_conns[index].webrtc, __ice_gathering_state_change_cb, &g_conns[index]);
        if (ret != WEBRTC_ERROR_NONE)
                g_print("failed to webrtc_set_ice_gathering_state_change_cb()\n");
        else
                g_print("webrtc_set_ice_gathering_state_change_cb() success\n");
 
-       ret = webrtc_set_ice_connection_state_change_cb(g_conns[index].webrtc, __ice_connection_state_change_cb, g_conns[index].webrtc);
+       ret = webrtc_set_ice_connection_state_change_cb(g_conns[index].webrtc, __ice_connection_state_change_cb, &g_conns[index]);
        if (ret != WEBRTC_ERROR_NONE)
                g_print("failed to webrtc_set_ice_connection_state_change_cb()\n");
        else
@@ -1773,75 +1944,6 @@ static void _webrtc_media_packet_source_unset_buffer_state_changed_cb(int index,
        g_print("webrtc_media_packet_source_unset_buffer_state_changed_cb() success\n");
 }
 
-static void _webrtc_create_offer(int index)
-{
-       int ret = WEBRTC_ERROR_NONE;
-
-       if (g_conns[index].offer) {
-               free(g_conns[index].offer);
-               g_conns[index].offer = NULL;
-       }
-
-       ret = webrtc_create_offer(g_conns[index].webrtc, NULL, &g_conns[index].offer);
-       RET_IF(ret != WEBRTC_ERROR_NONE, "ret[0x%x]", ret);
-
-       g_print("webrtc_create_offer() success\noffer:\n%s\n", g_conns[index].offer);
-}
-
-static void _webrtc_create_answer(int index)
-{
-       int ret = WEBRTC_ERROR_NONE;
-
-       if (g_conns[index].answer) {
-               free(g_conns[index].answer);
-               g_conns[index].answer = NULL;
-       }
-
-       ret = webrtc_create_answer(g_conns[index].webrtc, NULL, &g_conns[index].answer);
-       RET_IF(ret != WEBRTC_ERROR_NONE, "ret[0x%x]", ret);
-
-       g_print("webrtc_create_answer() success\nanswer:\n%s\n", g_conns[index].answer);
-}
-
-static void _webrtc_set_local_description(int index, bool is_offer)
-{
-       int ret = WEBRTC_ERROR_NONE;
-
-       ret = webrtc_set_local_description(g_conns[index].webrtc, is_offer ? g_conns[index].offer : g_conns[index].answer);
-       RET_IF(ret != WEBRTC_ERROR_NONE, "ret[0x%x]", ret);
-
-       g_print("webrtc_set_local_description() success\n");
-}
-
-static void _webrtc_set_remote_description(int index)
-{
-       int ret = WEBRTC_ERROR_NONE;
-
-       ret = webrtc_set_remote_description(g_conns[index].webrtc, g_conns[index].remote_desc);
-       RET_IF(ret != WEBRTC_ERROR_NONE, "ret[0x%x]", ret);
-
-       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_conns[g_conn_index].webrtc, (const char *)data);
-       RET_IF(ret != WEBRTC_ERROR_NONE, "ret[0x%x]", ret);
-
-       g_print("webrtc_ice_candidate() success\n");
-}
-
-static void _webrtc_add_ice_candidate(int index)
-{
-       g_list_foreach(g_conns[index].ice_candidates, __foreach_ice_candidate, NULL);
-
-       g_list_free_full(g_conns[index].ice_candidates, free);
-
-       g_conns[index].ice_candidates = NULL;
-}
-
 static void _webrtc_create_data_channel(int index)
 {
        int ret = WEBRTC_ERROR_NONE;
@@ -1932,6 +2034,31 @@ static void _request_session(int index, int remote_peer_id)
        }
 }
 
+static void _request_join_room(int index, char *room_name)
+{
+       gchar *msg;
+
+       RET_IF(!room_name, "room_name is NULL");
+
+       if (!g_conns[index].ws_conn && !g_conns[index].is_connected) {
+               g_printerr("server[%s] is not connected\n", g_signaling_server);
+               return;
+       }
+
+       RET_IF(!g_conns[index].ws_conn, "ws_conn is NULL");
+
+       if (soup_websocket_connection_get_state(g_conns[index].ws_conn) != SOUP_WEBSOCKET_STATE_OPEN) {
+               g_printerr("websocket is not opened\n");
+               return;
+       }
+
+       msg = g_strdup_printf("ROOM %s", room_name);
+
+       g_print("\n[to SERVER > %s]\n", msg);
+       soup_websocket_connection_send_text(g_conns[index].ws_conn, msg);
+       g_free(msg);
+}
+
 static void _send_local_description(int index, bool is_offer)
 {
        char *desc;
@@ -1948,7 +2075,7 @@ static void _send_local_description(int index, bool is_offer)
        if (g_conns[index].ws_conn)
                soup_websocket_connection_send_text(g_conns[index].ws_conn, desc);
        else
-               _webrtc_signaling_send_message(index, desc);
+               _webrtc_signaling_send_message(&g_conns[index], desc);
 }
 
 gulong _connect_signal(GObject *obj, const char *signal_name, GCallback callback, gpointer user_data)
@@ -2868,6 +2995,97 @@ static void __websocket_closed_cb(SoupWebsocketConnection *ws_conn, gpointer use
        __close_websocket(conn);
 }
 
+static void __auto_configure_add_peer(gchar *peer_id, bool is_offer)
+{
+       int i;
+
+       RET_IF(!peer_id, "peer_id is NULL");
+
+       if (!strcmp(peer_id,""))
+               return;
+
+       g_print("add peer[%s] to make an %s\n", peer_id, is_offer ? "offer" : "answer");
+
+       for (i = 0; i < MAX_CONNECTION_LEN; i++) {
+               unsigned int source_id = 0;
+
+               if (g_conns[i].webrtc)
+                       continue;
+
+               g_conns[i].remote_peer_id = atoi((const char *)peer_id);
+               g_conns[i].is_for_room = true;
+               g_conns[i].is_offer = is_offer;
+
+               _webrtc_create(i);
+               _webrtc_set_error_cb(i);
+               _webrtc_set_state_changed_cb(i);
+               _webrtc_set_ice_candidate_cb(i);
+               _webrtc_set_negotiation_needed_cb(i);
+               _webrtc_set_all_negotiation_state_change_cbs(i);
+
+               switch (g_conns[i].room_source_type) {
+               case 1:
+                       _webrtc_add_media_source(i, WEBRTC_MEDIA_SOURCE_TYPE_VIDEOTEST + 1, &source_id);
+                       _webrtc_media_source_set_video_loopback(i, source_id);
+                       _webrtc_add_media_source(i, WEBRTC_MEDIA_SOURCE_TYPE_AUDIOTEST + 1, NULL);
+                       break;
+               case 2:
+                       _webrtc_add_media_source(i, WEBRTC_MEDIA_SOURCE_TYPE_CAMERA + 1, &source_id);
+                       _webrtc_media_source_set_video_loopback(i, source_id);
+                       _webrtc_add_media_source(i, WEBRTC_MEDIA_SOURCE_TYPE_MIC + 1, NULL);
+                       break;
+               default:
+                       return;
+               }
+
+               _webrtc_start(i);
+               _webrtc_set_track_added_cb(i);
+               _webrtc_set_display_type(i, WEBRTC_DISPLAY_TYPE_EVAS);
+
+               return;
+       }
+       g_printerr("failed to __auto_configure_add_peer_to_offer()\n");
+}
+
+connection_s *__get_conn_from_peer_id(gchar *peer_id, int *index)
+{
+       int i;
+
+       if (!peer_id) {
+               g_printerr("peer_id is NULL\n");
+               return NULL;
+       }
+
+       for (i = 0; i < MAX_CONNECTION_LEN; i++) {
+               if (g_conns[i].remote_peer_id <= 0)
+                       continue;
+               if (g_conns[i].remote_peer_id == atoi((const char *)peer_id)) {
+                       if (index)
+                               *index = i;
+                       return &g_conns[i];
+               }
+       }
+
+       g_printerr("not found matching conn from peer_id[%s]", peer_id);
+       return NULL;
+}
+
+static void __auto_configure_release_peer(gchar *peer_id)
+{
+       connection_s *conn;
+       int i;
+
+       RET_IF(!peer_id, "peer_id is NULL");
+
+       g_print("release peer[%s] related resources\n", peer_id);
+
+       if (!(conn = __get_conn_from_peer_id(peer_id, &i)))
+               return;
+
+       _webrtc_stop(i);
+       _webrtc_destroy(i);
+}
+
 static void __handle_json_structured_message(connection_s *conn, const gchar *text)
 {
        JsonNode *root;
@@ -2899,9 +3117,25 @@ static void __handle_json_structured_message(connection_s *conn, const gchar *te
                        free(conn->remote_desc);
                conn->remote_desc = strdup(text);
 
+               if (conn->is_for_room) {
+                       webrtc_state_e state = WEBRTC_STATE_IDLE;
+                       webrtc_get_state(conn->webrtc, &state);
+                       if (state == WEBRTC_STATE_NEGOTIATING)
+                               _webrtc_set_remote_description(conn);
+               }
+
        } else if (json_object_has_member(object, "ice")){
                g_print("\n[from SERVER > ICE]\n%s\n", text);
-               conn->ice_candidates = g_list_append(conn->ice_candidates, strdup(text));
+               if (conn->is_for_room) {
+                       webrtc_state_e state = WEBRTC_STATE_IDLE;
+                       webrtc_get_state(conn->webrtc, &state);
+                       if (state == WEBRTC_STATE_NEGOTIATING)
+                               _webrtc_add_ice_candidate(conn, text);
+                       else
+                               conn->ice_candidates = g_list_append(conn->ice_candidates, strdup(text));
+               } else {
+                       conn->ice_candidates = g_list_append(conn->ice_candidates, strdup(text));
+               }
 
        } else {
                g_printerr("neither 'sdp' nor 'ice' member exist in JSON message [%s]\n", text);
@@ -2910,6 +3144,75 @@ static void __handle_json_structured_message(connection_s *conn, const gchar *te
        g_object_unref(parser);
 }
 
+static void __auto_configure_handle_room_message(gchar *peer_id, gchar *message)
+{
+       connection_s *conn;
+
+       RET_IF(!peer_id, "peer_id is NULL");
+       RET_IF(!message, "message is NULL");
+
+       g_print("peer[%s], message[%s]\n", peer_id, message);
+
+       if (!(conn = __get_conn_from_peer_id(peer_id, NULL)))
+               return;
+
+       __handle_json_structured_message(conn, message);
+}
+
+static void __handle_room_related_message(connection_s *conn, const gchar *text)
+{
+       gchar **tokens = NULL;
+       guint len;
+       int i;
+
+       RET_IF(!conn, "conn is NULL");
+       RET_IF(!text, "text is NULL");
+
+       if (g_str_has_prefix(text, "ROOM_OK")) {
+               g_print("\n[from SERVER > %s]\n", text);
+               conn->server_status = SERVER_STATUS_ROOM_ESTABLISHED;
+
+               /* parse text, get the previous peers in the room and negotiate to each other */
+               if (strlen(text) > strlen("ROOM_OK")) {
+                       /* e.g. ROOM_OK 1234 5311 324 */
+                       tokens = g_strsplit(text, " ", 0);
+                       len = g_strv_length(tokens);
+                       for (i = 1; i < len; i++) {
+                               if (i > MAX_CONNECTION_LEN) {
+                                       g_print("connection[%d], out of range\n", i);
+                                       break;
+                               }
+                               __auto_configure_add_peer(tokens[i], true);
+                       }
+               }
+
+       } else if (g_str_has_prefix(text, "ROOM_PEER_JOINED")) {
+               g_print("\n[from SERVER > %s]\n", text);
+               /* e.g. ROOM_PEER_JOINED 1234 */
+               tokens = g_strsplit(text, " ", 2);
+               __auto_configure_add_peer(tokens[1], false);
+
+       } else if (g_str_has_prefix(text, "ROOM_PEER_LEFT")) {
+               g_print("\n[from SERVER > %s]\n", text);
+               /* e.g. ROOM_PEER_LEFT 1234 */
+               tokens = g_strsplit(text, " ", 2);
+               __auto_configure_release_peer(tokens[1]);
+
+       } else if (g_str_has_prefix(text, "ROOM_PEER_MSG")) {
+               g_print("\n[from SERVER > %s]\n", text);
+               /* e.g. ROOM_PEER_MSG 1234 msg */
+               tokens = g_strsplit(text, " ", 3);
+               __auto_configure_handle_room_message(tokens[1], tokens[2]);
+
+       } else {
+               g_print("\n[from SERVER > %s]\n", text);
+               conn->server_status = SERVER_STATUS_ERROR_FOUND;
+       }
+
+       if (tokens)
+               g_strfreev(tokens);
+}
+
 static void __websocket_message_cb(SoupWebsocketConnection *ws_conn, SoupWebsocketDataType type, GBytes *message, gpointer user_data)
 {
        gchar *text;
@@ -2927,12 +3230,15 @@ static void __websocket_message_cb(SoupWebsocketConnection *ws_conn, SoupWebsock
 
        /* NOTE: Logics below can be different in each server */
        if (g_strcmp0(text, "HELLO") == 0) {
-               g_print("\n[from SERVER > %s] registered done, g_local_peer_id[%d]\n", text, conn->local_peer_id);
+               g_print("\n[from SERVER > %s] registered done, local_peer_id[%d]\n", text, conn->local_peer_id);
 
        } else if (g_strcmp0(text, "SESSION_OK") == 0) {
                g_print("\n[from SERVER > %s]\n", text);
                conn->server_status = SERVER_STATUS_SESSION_ESTABLISHED;
 
+       } else if (g_str_has_prefix(text, "ROOM_")) {
+               __handle_room_related_message(conn, text);
+
        } else if (g_str_has_prefix(text, "ERROR")) {
                g_print("\n[from SERVER > %s]\n", text);
                conn->server_status = SERVER_STATUS_ERROR_FOUND;
@@ -3223,16 +3529,16 @@ void _interpret_main_menu(char *cmd)
                        g_conns[g_conn_index].menu_state = CURRENT_STATUS_MEDIA_PACKET_SOURCE_UNSET_BUFFER_STATE_CHANGED_CB;
 
                } else if (strncmp(cmd, "co", 2) == 0) {
-                       _webrtc_create_offer(g_conn_index);
+                       _webrtc_create_offer(&g_conns[g_conn_index]);
 
                } else if (strncmp(cmd, "ca", 2) == 0) {
-                       _webrtc_create_answer(g_conn_index);
+                       _webrtc_create_answer(&g_conns[g_conn_index]);
 
                } else if (strncmp(cmd, "sl", 2) == 0) {
                        g_conns[g_conn_index].menu_state = CURRENT_STATUS_SET_LOCAL_DESCRIPTION;
 
                } else if (strncmp(cmd, "sr", 2) == 0) {
-                       _webrtc_set_remote_description(g_conn_index);
+                       _webrtc_set_remote_description(&g_conns[g_conn_index]);
 
                } else if (strncmp(cmd, "st", 2) == 0) {
                        g_conns[g_conn_index].menu_state = CURRENT_STATUS_SET_STUN_SERVER;
@@ -3261,6 +3567,9 @@ void _interpret_main_menu(char *cmd)
                } else if (strncmp(cmd, "rs", 2) == 0) {
                        g_conns[g_conn_index].menu_state = CURRENT_STATUS_REQUEST_SESSION;
 
+               } else if (strncmp(cmd, "rj", 2) == 0) {
+                       g_conns[g_conn_index].menu_state = CURRENT_STATUS_REQUEST_JOIN_ROOM;
+
                } else if (strncmp(cmd, "sd", 2) == 0) {
                        g_conns[g_conn_index].menu_state = CURRENT_STATUS_SEND_LOCAL_DESCRIPTION;
 
@@ -3274,7 +3583,7 @@ void _interpret_main_menu(char *cmd)
                        g_conns[g_conn_index].menu_state = CURRENT_STATUS_SET_MEDIA_PATH_TO_MEDIA_FILE_SOURCE;
 
                } else if (strncmp(cmd, "ac", 2) == 0) {
-                       _webrtc_add_ice_candidate(g_conn_index);
+                       _webrtc_add_ice_candidate(&g_conns[g_conn_index], NULL);
 
                } else {
                        g_print("unknown menu \n");
@@ -3385,7 +3694,7 @@ void display_setting_status()
                                g_print("  [*]");
                        else
                                g_print("  [ ]");
-                       g_print("[%d][%s]\n", i, g_server_status_str[g_conns[i].server_status]);
+                       g_print("[%d][%s][%d]\n", i, g_server_status_str[g_conns[i].server_status], g_conns[i].local_peer_id);
                }
        }
        g_print("-----------------------------------------------------------------------------------------\n");
@@ -3463,6 +3772,7 @@ void display_sub_basic()
        g_print("cc. Change connection\n");
        g_print("cs. Connect to the signaling server\n");
        g_print("rs. Request session of remote peer id\n");
+       g_print("rj. Request join room\n");
        g_print("sd. Send local description\n");
        g_print("sp. Start pushing packet to media packet source\t");
        g_print("tp. Stop pushing packet to media packet source\n");
@@ -3590,6 +3900,12 @@ static void displaymenu()
        } else if (g_conns[g_conn_index].menu_state == CURRENT_STATUS_REQUEST_SESSION) {
                g_print("*** input remote peer id.\n");
 
+       } else if (g_conns[g_conn_index].menu_state == CURRENT_STATUS_REQUEST_JOIN_ROOM) {
+               if (g_conns[g_conn_index].cnt == 0)
+                       g_print("*** input source type.(1:videotest/audiotest 2:camera/mic)\n");
+               else if (g_conns[g_conn_index].cnt == 1)
+                       g_print("*** input room name to join.\n");
+
        } else if (g_conns[g_conn_index].menu_state == CURRENT_STATUS_SEND_LOCAL_DESCRIPTION) {
                g_print("*** input type of local description to send to the server.(1:offer, 2:answer)\n");
 
@@ -3649,7 +3965,7 @@ static void interpret(char *cmd)
                break;
        case CURRENT_STATUS_ADD_MEDIA_SOURCE: {
                value = atoi(cmd);
-               _webrtc_add_media_source(g_conn_index, value);
+               _webrtc_add_media_source(g_conn_index, value, NULL);
                reset_menu_state();
                break;
        }
@@ -3860,9 +4176,9 @@ static void interpret(char *cmd)
        case CURRENT_STATUS_SET_LOCAL_DESCRIPTION: {
                value = atoi(cmd);
                if (value == 1)
-                       _webrtc_set_local_description(g_conn_index, true);
+                       _webrtc_set_local_description(&g_conns[g_conn_index], true);
                else if (value == 2)
-                       _webrtc_set_local_description(g_conn_index, false);
+                       _webrtc_set_local_description(&g_conns[g_conn_index], false);
                else
                        g_print("invalid value[%d]\n", value);
 
@@ -3894,6 +4210,26 @@ static void interpret(char *cmd)
                reset_menu_state();
                break;
        }
+       case CURRENT_STATUS_REQUEST_JOIN_ROOM: {
+               switch (g_conns[g_conn_index].cnt) {
+               case 0:
+                       value = atoi(cmd);
+                       if (value <= 0 || value > 2) {
+                               g_printerr("invalid value[%d]\n", value);
+                               reset_menu_state();
+                               break;
+                       }
+                       g_conns[g_conn_index].room_source_type = value;
+                       g_conns[g_conn_index].cnt++;
+                       break;
+               case 1:
+                       _request_join_room(g_conn_index, cmd);
+                       g_conns[g_conn_index].cnt = 0;
+                       reset_menu_state();
+                       break;
+               }
+               break;
+       }
        case CURRENT_STATUS_SEND_LOCAL_DESCRIPTION: {
                value = atoi(cmd);
                if (value == 1)