Add WebRTC signaling example to test app 57/235257/13
authorHyunil <hyunil46.park@samsung.com>
Wed, 3 Jun 2020 06:29:18 +0000 (15:29 +0900)
committerSangchul Lee <sc11.lee@samsung.com>
Tue, 16 Jun 2020 07:53:05 +0000 (16:53 +0900)
- The websocket handshake example is added

[Version] 0.1.65
[Issue Type] Test

Change-Id: Iae0cfe586265787273d2e50c76aa465a53d253e3
Signed-off-by: Hyunil <hyunil46.park@samsung.com>
packaging/capi-media-streamer.spec
test/CMakeLists.txt
test/media_streamer_test.c

index 63ac8a5..06e4351 100644 (file)
@@ -1,6 +1,6 @@
 Name:       capi-media-streamer
 Summary:    A Media Streamer API
-Version:    0.1.64
+Version:    0.1.65
 Release:    0
 Group:      Multimedia/API
 License:    Apache-2.0
index d816bb4..1d45195 100755 (executable)
@@ -6,12 +6,12 @@ INCLUDE_DIRECTORIES(../include)
 link_directories(${CMAKE_SOURCE_DIR}/../)
 
 INCLUDE(FindPkgConfig)
-pkg_check_modules(${fw_test} REQUIRED glib-2.0 elementary evas ecore-wl2 appcore-efl)
+pkg_check_modules(${fw_test} REQUIRED glib-2.0 elementary evas ecore-wl2 appcore-efl json-glib-1.0 libsoup-2.4)
 FOREACH(flag ${${fw_test}_CFLAGS})
     SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} ${flag}")
 ENDFOREACH(flag)
 
-SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${EXTRA_CFLAGS} -pie")
+SET(CMAKE_C_FLAGS "-I/usr/include/json-glib-1.0 ${CMAKE_C_FLAGS} ${EXTRA_CFLAGS} -pie")
 
 aux_source_directory(. sources)
 
index 4691731..a0a9cc9 100644 (file)
@@ -23,6 +23,9 @@
 #include <Elementary.h>
 #include <appcore-efl.h>
 
+/* For signalling */
+#include <libsoup/soup.h>
+#include <json-glib/json-glib.h>
 #include <media_streamer.h>
 
 #ifdef PACKAGE
 #endif
 #define PACKAGE "media_streamer_test"
 
+/* webrtc */
+enum AppState {
+       APP_STATE_UNKNOWN = 0,
+       APP_STATE_ERROR = 1, /* generic error */
+       SERVER_CONNECTING = 1000,
+       SERVER_CONNECTION_ERROR,
+       SERVER_CONNECTED, /* Ready to register */
+       SERVER_REGISTERING = 2000,
+       SERVER_REGISTRATION_ERROR,
+       SERVER_REGISTERED, /* Ready to call a peer */
+       SERVER_CLOSED, /* server connection closed by us or the server */
+       PEER_CONNECTING = 3000,
+       PEER_CONNECTION_ERROR,
+       PEER_CONNECTED,
+       PEER_CALL_NEGOTIATING = 4000,
+       PEER_CALL_WAITING,
+       PEER_CALL_STARTED,
+       PEER_CALL_STOPPING,
+       PEER_CALL_STOPPED,
+       PEER_CALL_ERROR,
+};
+
 typedef enum {
        MENU_STATE_UNKNOWN = 0,
        MENU_STATE_MAIN_MENU,
@@ -122,6 +147,8 @@ typedef enum {
 #define DEFAULT_SEGMENT_PATH "/tmp/segment%05d.ts"
 #define DEFAULT_PLAYLIST_PATH "/tmp/playlist.m3u8"
 
+#define ENTER g_print ("%s:%d>%s\n",__FILE__, __LINE__, __FUNCTION__);
+
 /*---------------------------------------------------------------------------
 |    GLOBAL VARIABLE DEFINITIONS:                     |
 ---------------------------------------------------------------------------*/
@@ -153,6 +180,12 @@ gboolean g_autoplug_mode = FALSE;
 gboolean g_video_is_on = FALSE;
 gboolean g_audio_is_on = FALSE;
 gboolean g_use_proxy = FALSE;
+static gint32 our_id = 0;
+static SoupWebsocketConnection *ws_conn = NULL;
+static const gchar *server_url = "wss://webrtc.nirbheek.in:8443";
+static enum AppState app_state = 0;
+static gboolean disable_ssl = FALSE;
+media_streamer_node_h webrtcbin = NULL;
 
 media_format_h vfmt_vp8 = NULL;
 media_format_h vfmt_i420 = NULL;
@@ -430,6 +463,331 @@ static void create_formats(void)
                g_print("media_format_set_container_mime failed!\n");
 }
 
+static gboolean
+cleanup_webrtc (const gchar * msg, enum AppState state)
+{
+       ENTER;
+       if (msg)
+               g_printerr ("%s\n", msg);
+       if (state > 0)
+               app_state = state;
+
+       if (ws_conn) {
+               if (soup_websocket_connection_get_state (ws_conn) ==
+                       SOUP_WEBSOCKET_STATE_OPEN)
+                       /* This will call us again */
+                       soup_websocket_connection_close (ws_conn, 1000, "");
+               else
+               g_object_unref (ws_conn);
+       }
+       /* To allow usage as a GSourceFunc */
+       return G_SOURCE_REMOVE;
+}
+
+static gboolean
+setup_call (void)
+{
+       gchar *msg;
+       ENTER;
+
+       if (soup_websocket_connection_get_state (ws_conn) !=
+               SOUP_WEBSOCKET_STATE_OPEN)
+               return FALSE;
+       if (!g_peer_id)
+               return FALSE;
+
+       g_print ("Setting up signalling server call with %d\n", g_peer_id);
+       app_state = PEER_CONNECTING;
+       msg = g_strdup_printf ("SESSION %d", g_peer_id);
+       soup_websocket_connection_send_text (ws_conn, msg);
+       g_free (msg);
+       return TRUE;
+}
+
+/* One mega message handler for our asynchronous calling mechanism */
+static void
+on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type,
+    GBytes * message, gpointer user_data)
+{
+       gchar *text;
+       ENTER;
+
+       switch (type) {
+       case SOUP_WEBSOCKET_DATA_BINARY:
+               g_printerr ("Received unknown binary message, ignoring\n");
+               return;
+       case SOUP_WEBSOCKET_DATA_TEXT: {
+               gsize size;
+               const gchar *data = g_bytes_get_data (message, &size);
+               /* Convert to NULL-terminated string */
+               text = g_strndup (data, size);
+               g_print ("Received text message, [%s]\n", text);
+               break;
+       }
+       default:
+               g_assert_not_reached ();
+       }
+
+       /* Server has accepted our registration, we are ready to send commands */
+       if (g_strcmp0 (text, "HELLO") == 0) {
+               if (app_state != SERVER_REGISTERING) {
+                       cleanup_webrtc ("ERROR: Received HELLO when not registering",
+                       APP_STATE_ERROR);
+                       goto out;
+               }
+               app_state = SERVER_REGISTERED;
+               g_print ("Registered with server\n");
+               /* Ask signalling server to connect us with a specific peer */
+               if (g_peer_id) {
+                       if (!setup_call ()) {
+                               cleanup_webrtc ("ERROR: Failed to setup call", PEER_CALL_ERROR);
+                               goto out;
+                       }
+               } else {
+                       /* should WAIT for another peer */
+                       g_print ("need to wait for another peer...(our id:%d)\n", our_id);
+                       app_state = PEER_CALL_WAITING;
+                       /* Start negotiation (exchange SDP and ICE candidates) */
+                       /* need to start pipeline_answer */
+               }
+       /* Call has been setup by the server, now we can start negotiation */
+       } else if (g_strcmp0 (text, "SESSION_OK") == 0) {
+               if (app_state != PEER_CONNECTING) {
+                       cleanup_webrtc ("ERROR: Received SESSION_OK when not calling",
+                       PEER_CONNECTION_ERROR);
+                       goto out;
+               }
+
+           app_state = PEER_CONNECTED;
+               /* Start negotiation (exchange SDP and ICE candidates) */
+               /* need to start pipeline */
+       /* Handle errors */
+       } else if (g_str_has_prefix (text, "ERROR")) {
+               switch (app_state) {
+               case SERVER_CONNECTING:
+                       app_state = SERVER_CONNECTION_ERROR;
+                       break;
+               case SERVER_REGISTERING:
+                       app_state = SERVER_REGISTRATION_ERROR;
+                       break;
+               case PEER_CONNECTING:
+                       app_state = PEER_CONNECTION_ERROR;
+                       break;
+               case PEER_CALL_WAITING:
+               case PEER_CONNECTED:
+               case PEER_CALL_NEGOTIATING:
+                       app_state = PEER_CALL_ERROR;
+                       break;
+               default:
+                       app_state = APP_STATE_ERROR;
+    }
+       cleanup_webrtc (text, 0);
+       /* Look for JSON messages containing SDP and ICE candidates */
+       } else {
+               JsonNode *root;
+               JsonObject *object, *child;
+               JsonParser *parser = json_parser_new ();
+               if (!json_parser_load_from_data (parser, text, -1, NULL)) {
+                       g_printerr ("Unknown message '%s', ignoring", text);
+                       g_object_unref (parser);
+                       goto out;
+               }
+
+               root = json_parser_get_root (parser);
+               if (!JSON_NODE_HOLDS_OBJECT (root)) {
+                       g_printerr ("Unknown json message '%s', ignoring", text);
+                       g_object_unref (parser);
+                       goto out;
+               }
+
+               object = json_node_get_object (root);
+               /* Check type of JSON message */
+               if (json_object_has_member (object, "sdp")) {
+                       const gchar *text, *sdptype;
+
+                       if (g_peer_id)
+                               g_assert_cmphex (app_state, ==, PEER_CALL_NEGOTIATING);
+                       else
+                       g_assert_cmphex (app_state, ==, PEER_CALL_WAITING);
+
+                       child = json_object_get_object_member (object, "sdp");
+
+                       if (!json_object_has_member (child, "type")) {
+                               cleanup_webrtc ("ERROR: received SDP without 'type'",
+                               PEER_CALL_ERROR);
+                               goto out;
+                       }
+
+                       sdptype = json_object_get_string_member (child, "type");
+                       text = json_object_get_string_member (child, "sdp");
+
+
+                       if (g_str_equal (sdptype, "answer")) {
+                               g_print ("Received answer:\n%s\n", text);
+
+                               /* need to API: set remote description */
+
+                               app_state = PEER_CALL_STARTED;
+                       } else {
+                               g_print ("Received offer:\n%s\n", text);
+                               /* need to API: set remote description */
+                       }
+
+               } else if (json_object_has_member (object, "ice")) {
+                       /*need to API: Add ice candidate sent by remote peer */
+               } else {
+                       g_printerr ("Ignoring unknown JSON message:\n%s\n", text);
+               }
+               g_object_unref (parser);
+       }
+
+out:
+       g_free (text);
+}
+
+static gint32
+register_with_server (void)
+{
+       gchar *hello;
+       gint32 our_id;
+       ENTER;
+
+       if (soup_websocket_connection_get_state (ws_conn) !=
+               SOUP_WEBSOCKET_STATE_OPEN)
+               return -1;
+
+       our_id = g_random_int_range (10, 10000);
+       g_print ("Registering id %i with server\n", our_id);
+       app_state = SERVER_REGISTERING;
+
+       /* Register with the server with a random integer id. Reply will be received
+        * by on_server_message() */
+       hello = g_strdup_printf ("HELLO %i", our_id);
+       soup_websocket_connection_send_text (ws_conn, hello);
+       g_free (hello);
+
+       return our_id;
+}
+
+/* Answer created by our pipeline, to be sent to the peer */
+static void
+on_server_closed (SoupWebsocketConnection * conn G_GNUC_UNUSED,
+    gpointer user_data G_GNUC_UNUSED)
+{
+       app_state = SERVER_CLOSED;
+       ENTER;
+
+       cleanup_webrtc ("Server connection closed", 0);
+}
+
+static void
+on_server_connected (SoupSession * session, GAsyncResult * res,
+    SoupMessage *msg)
+{
+       GError *error = NULL;
+       ENTER;
+
+       g_print("on_server_connected\n");
+       ws_conn = soup_session_websocket_connect_finish (session, res, &error);
+       if (error) {
+               cleanup_webrtc (error->message, SERVER_CONNECTION_ERROR);
+               g_error_free (error);
+               return;
+       }
+
+       g_assert_nonnull (ws_conn);
+
+       app_state = SERVER_CONNECTED;
+       g_print ("Connected to signalling server\n");
+
+       g_signal_connect (ws_conn, "closed", G_CALLBACK (on_server_closed), NULL);
+       g_signal_connect (ws_conn, "message", G_CALLBACK (on_server_message), NULL);
+
+       /* Register with the server so it knows about us and can accept commands */
+       our_id = register_with_server ();
+}
+
+/* TIZEN: add for log */
+static inline gchar
+gst_soup_util_log_make_level_tag (SoupLoggerLogLevel level)
+{
+       gchar c;
+
+       if (G_UNLIKELY ((gint) level > 9))
+               return '?';
+
+       switch (level) {
+       case SOUP_LOGGER_LOG_MINIMAL:
+               c = 'M';
+               break;
+       case SOUP_LOGGER_LOG_HEADERS:
+               c = 'H';
+               break;
+       case SOUP_LOGGER_LOG_BODY:
+               c = 'B';
+               break;
+       default:
+               /* Unknown level. If this is hit libsoup likely added a new
+               * log level to SoupLoggerLogLevel and it should be added
+               * as a case */
+               c = level + '0';
+               break;
+       }
+       return c;
+}
+
+static void
+_log_printer_cb (SoupLogger G_GNUC_UNUSED * logger,
+    SoupLoggerLogLevel level, char direction, const char *data,
+    gpointer user_data)
+{
+       gchar c;
+
+       c = gst_soup_util_log_make_level_tag (level);
+       g_print("HTTP_SESSION(%c): %c %s\n", c, direction, data);
+}
+
+static void
+connect_to_websocket_server_async (void)
+{
+       SoupLogger *logger;
+       SoupMessage *message;
+       SoupSession *session;
+       SoupURI *proxy_uri;
+       const char *https_aliases[] = {"wss", NULL};
+       ENTER;
+
+       if (!g_use_proxy){
+               session = soup_session_new_with_options (SOUP_SESSION_SSL_STRICT, !disable_ssl,
+                       SOUP_SESSION_HTTPS_ALIASES, https_aliases, NULL);
+       } else {
+           proxy_uri = soup_uri_new (g_proxy_address);
+               session = soup_session_new_with_options (SOUP_SESSION_SSL_STRICT, !disable_ssl,
+                       SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE,
+                       SOUP_SESSION_PROXY_URI, proxy_uri,
+                       SOUP_SESSION_SSL_CA_FILE, "/opt/var/lib/ca-certificates/ca-bundle.pem",
+                       SOUP_SESSION_HTTPS_ALIASES, https_aliases, NULL);
+                       soup_uri_free (proxy_uri);
+       }
+
+       logger = soup_logger_new (SOUP_LOGGER_LOG_BODY, -1);
+
+       /* TIZEN: add for log */
+       soup_logger_set_printer (logger, _log_printer_cb, NULL, NULL);
+
+       soup_session_add_feature (session, SOUP_SESSION_FEATURE (logger));
+       g_object_unref (logger);
+
+       message = soup_message_new (SOUP_METHOD_GET, server_url);
+
+       g_print ("Connecting to server[%s]...\n", server_url);
+
+       /* Once connected, we will register */
+       soup_session_websocket_connect_async (session, message, NULL, NULL, NULL,
+               (GAsyncReadyCallback) on_server_connected, message);
+       app_state = SERVER_CONNECTING;
+}
+
 static void set_rtp_params(media_streamer_node_h rtp_node, const char *ip, int video_port, int audio_port, gboolean port_reverse)
 {
        bundle *params = bundle_create();
@@ -632,11 +990,6 @@ static void _create_videotestsrc_capsfilter_videosink(void)
        media_streamer_node_link(videoconverter, "src", video_sink, "sink");
 }
 
-static void _create_webrtc_sendrec_video_audio(void)
-{
-
-}
-
 static void _create_file_sub_playing(void)
 {
        media_streamer_node_h file_sub_src = NULL;
@@ -1544,7 +1897,7 @@ void run_webrtc_preset(void)
 {
        if ((g_scenario_mode == SCENARIO_MODE_WEBRTC_SENDRECV_VIDEO_AUDIO)) {
                create_formats();
-               _create_webrtc_sendrec_video_audio();
+               connect_to_websocket_server_async ();
        } else {
                g_print("Invalid scenario menu preset was selected!");
        }