From 33b4e0ccb610b30e07124ad0dc7b81257067f7d3 Mon Sep 17 00:00:00 2001 From: Chanhee Lee Date: Wed, 23 Nov 2022 09:33:42 +0900 Subject: [PATCH] Apply subscriber stream interface to Android WebRTC [Problem] Initial Android stream interfaces are added but Android WebRTC is still using transport interfaces. [Solution] Update Android WebRTC first with the subscriber stream interface. --- .../main/java/com/samsung/android/aitt/Aitt.java | 102 ++++++++++-- .../android/aitt/handler/ModuleHandler.java | 7 + .../samsung/android/aitt/handler/RTSPHandler.java | 4 +- .../android/aitt/handler/StreamHandler.java | 2 +- .../android/aitt/handler/TransportHandler.java | 7 - .../android/aitt/handler/WebRTCHandler.java | 107 +++---------- .../samsung/android/aitt/stream/AittStream.java | 14 +- .../samsung/android/aitt/stream/RTSPStream.java | 15 +- .../samsung/android/aitt/stream/WebRTCStream.java | 171 +++++++++++++++++++++ .../android/modules/rtsp/RTSPInstrumentedTest.java | 2 +- android/modules/webrtc/build.gradle | 14 +- .../modules/webrtc/WebRTCInstrumentedTest.java | 140 +++++++++++++++++ .../modules/webrtc/src/main/AndroidManifest.xml | 2 +- .../com/samsung/android/modules/webrtc/WebRTC.java | 18 ++- .../android/modules/webrtc/WebRTCServer.java | 25 ++- .../android/modules/webrtc/ExampleUnitTest.java | 17 -- 16 files changed, 493 insertions(+), 154 deletions(-) create mode 100644 android/aitt/src/main/java/com/samsung/android/aitt/stream/WebRTCStream.java create mode 100644 android/modules/webrtc/src/androidTest/java/com/samsung/android/modules/webrtc/WebRTCInstrumentedTest.java delete mode 100644 android/modules/webrtc/src/test/java/com/samsung/android/modules/webrtc/ExampleUnitTest.java diff --git a/android/aitt/src/main/java/com/samsung/android/aitt/Aitt.java b/android/aitt/src/main/java/com/samsung/android/aitt/Aitt.java index f5d4f60..9843225 100644 --- a/android/aitt/src/main/java/com/samsung/android/aitt/Aitt.java +++ b/android/aitt/src/main/java/com/samsung/android/aitt/Aitt.java @@ -27,6 +27,7 @@ import androidx.annotation.Nullable; import com.google.flatbuffers.FlexBuffers; import com.samsung.android.aitt.stream.AittStream; +import com.samsung.android.aitt.stream.WebRTCStream; import com.samsung.android.aittnative.JniInterface; import java.nio.ByteBuffer; @@ -45,14 +46,17 @@ import java.util.Map; * 6. API to set MQTT Connection Callback */ public class Aitt { + private static final String TAG = "AITT_ANDROID"; private static final String INVALID_TOPIC = "Invalid topic"; + private final Map publishTable = new HashMap<>(); private final Map> subscribeMap = new HashMap<>(); private final JniInterface mJniInterface; private final String ip; private final Context appContext; private final long instance; + private Map> subscribeCallbacks = new HashMap<>(); private Map aittSubId = new HashMap<>(); private ConnectionCallback connectionCallback = null; @@ -289,9 +293,8 @@ public class Aitt { Log.e(TAG, "Pair for port: " + port + "is null."); continue; } - Object transportHandler = protocolPair.second; if (protocolPair.first == protocol) - publishHandler(protocol, portTable, topic, transportHandler, hostIp, port, message); + publishHandler(protocol, portTable, topic, protocolPair.second, hostIp, port, message); } } } @@ -301,32 +304,77 @@ public class Aitt { } } + // TODO: Update publish with proper stream interface. + public boolean publish(AittStream stream, String topic, byte[] message, Protocol protocol) { + if (stream == null) { + Log.e(TAG, "Stream is null."); + return false; + } + + try { + synchronized (this) { + if (!publishTable.containsKey(topic)) { + Log.e(TAG, "Invalid publish request over unsubscribed topic"); + return false; + } + HostTable hostTable = publishTable.get(topic); + if (hostTable == null) { + Log.d(TAG, "Host table for topic [" + topic + "] is null."); + return false; + } + for (String hostIp : hostTable.hostMap.keySet()) { + PortTable portTable = hostTable.hostMap.get(hostIp); + if (portTable == null) { + Log.e(TAG, "Port table for host [" + hostIp + "] is null."); + continue; + } + for (Integer port : portTable.portMap.keySet()) { + Pair protocolPair = portTable.portMap.get(port); + if (protocolPair == null) { + Log.e(TAG, "Pair for port: " + port + "is null."); + continue; + } + if (protocolPair.first != protocol) { + Log.d(TAG, "protocol is not matched."); + continue; + } + + return stream.publish(topic, hostIp, port, message); + } + } + } + } catch (Exception e) { + Log.e(TAG, "Error during publish", e); + } + + return false; + } + /** * Method to create transportHandler and publish message based on protocol * - * @param protocol protocol using which data needs to be published - * @param portTable portTable has information about port and associated protocol with transport Handler object - * @param topic The topic to which data is published - * @param transportHandlerObject transportHandler object used to publish message - * @param ip IP address of the destination - * @param port Port number of the destination - * @param message Data to be transferred over WebRTC + * @param protocol protocol using which data needs to be published + * @param portTable portTable has information about port and associated protocol with transport Handler object + * @param topic The topic to which data is published + * @param moduleHandlerObject transportHandler object used to publish message + * @param ip IP address of the destination + * @param port Port number of the destination + * @param message Data to be transferred over WebRTC */ - private void publishHandler(Protocol protocol, PortTable portTable, String topic, Object transportHandlerObject, String ip, int port, byte[] message) { + private void publishHandler(Protocol protocol, PortTable portTable, String topic, Object moduleHandlerObject, String ip, int port, byte[] message) { // TODO: Validate protocol type. TransportHandler transportHandler; - if (transportHandlerObject == null) { + if (moduleHandlerObject == null) { transportHandler = (TransportHandler) createModuleHandler(protocol); if (transportHandler != null) transportHandler.setAppContext(appContext); portTable.portMap.replace(port, new Pair<>(protocol, transportHandler)); } else { - transportHandler = (TransportHandler) transportHandlerObject; + transportHandler = (TransportHandler) moduleHandlerObject; } - if (transportHandler != null) { + if (transportHandler != null) transportHandler.publish(topic, ip, port, message); - } } /** @@ -664,6 +712,18 @@ public class Aitt { return; } + // TODO: Handle multiple ports with the same topic. + if (portTable.portMap.isEmpty() == false) { + StringBuilder portLists = new StringBuilder(); + for (int p : portTable.portMap.keySet()) { + portLists.append(p); + portLists.append(", "); + } + Log.d(TAG, "Existing ports list: " + portLists); + portTable.portMap.clear(); + } + + Log.d(TAG, "[updatePublishTable] port " + port + "is added. (Topic,Host) = (" + topic + "," + host + ")"); portTable.portMap.put(port, new Pair<>(protocol, null)); } } @@ -707,15 +767,23 @@ public class Aitt { } public AittStream createStream(Protocol protocol, String topic, AittStream.StreamRole streamRole) { - // TODO: update this function. ModuleHandler moduleHandler = createModuleHandler(protocol); + if (moduleHandler != null && protocol == Protocol.WEBRTC) { + WebRTCStream webRTCStream = (WebRTCStream) ((WebRTCHandler) moduleHandler).newStreamModule(protocol, topic, streamRole, appContext); + if (webRTCStream != null && streamRole == AittStream.StreamRole.SUBSCRIBER) { + webRTCStream.setSelfIP(ip); + webRTCStream.setJNIInterface(mJniInterface); + } + + return webRTCStream; + } if (moduleHandler != null && protocol == Protocol.RTSP) - return ((RTSPHandler) moduleHandler).newStreamModule(protocol, topic, streamRole); + return ((RTSPHandler) moduleHandler).newStreamModule(protocol, topic, streamRole, appContext); + return null; } public void destroyStream(AittStream aittStream) { // TODO: implement this function. } - } diff --git a/android/aitt/src/main/java/com/samsung/android/aitt/handler/ModuleHandler.java b/android/aitt/src/main/java/com/samsung/android/aitt/handler/ModuleHandler.java index 15c236a..ec93b78 100644 --- a/android/aitt/src/main/java/com/samsung/android/aitt/handler/ModuleHandler.java +++ b/android/aitt/src/main/java/com/samsung/android/aitt/handler/ModuleHandler.java @@ -24,4 +24,11 @@ public interface ModuleHandler { * @param appContext application context */ void setAppContext(Context appContext); + + /** + * Interface to implement handler data callback mechanism + */ + interface HandlerDataCallback { + void pushHandlerData(byte[] data); + } } diff --git a/android/aitt/src/main/java/com/samsung/android/aitt/handler/RTSPHandler.java b/android/aitt/src/main/java/com/samsung/android/aitt/handler/RTSPHandler.java index 62f48d8..731ca41 100644 --- a/android/aitt/src/main/java/com/samsung/android/aitt/handler/RTSPHandler.java +++ b/android/aitt/src/main/java/com/samsung/android/aitt/handler/RTSPHandler.java @@ -15,6 +15,8 @@ */ package com.samsung.android.aitt.handler; +import android.content.Context; + import com.samsung.android.aitt.Aitt; import com.samsung.android.aitt.stream.AittStream; import com.samsung.android.aitt.stream.RTSPStream; @@ -22,7 +24,7 @@ import com.samsung.android.aitt.stream.RTSPStream; public class RTSPHandler extends StreamHandler { @Override - public AittStream newStreamModule(Aitt.Protocol protocol, String topic, AittStream.StreamRole role) { + public AittStream newStreamModule(Aitt.Protocol protocol, String topic, AittStream.StreamRole role, Context context) { // TODO: implement this function. return new RTSPStream(); } diff --git a/android/aitt/src/main/java/com/samsung/android/aitt/handler/StreamHandler.java b/android/aitt/src/main/java/com/samsung/android/aitt/handler/StreamHandler.java index 2fa6dc2..d54e08c 100644 --- a/android/aitt/src/main/java/com/samsung/android/aitt/handler/StreamHandler.java +++ b/android/aitt/src/main/java/com/samsung/android/aitt/handler/StreamHandler.java @@ -22,7 +22,7 @@ import com.samsung.android.aitt.stream.AittStream; public class StreamHandler implements ModuleHandler { - AittStream newStreamModule(Aitt.Protocol protocol, String topic, AittStream.StreamRole role) { + AittStream newStreamModule(Aitt.Protocol protocol, String topic, AittStream.StreamRole role, Context context) { // TODO: Change this function properly after refactoring WebRTC modules. return null; } diff --git a/android/aitt/src/main/java/com/samsung/android/aitt/handler/TransportHandler.java b/android/aitt/src/main/java/com/samsung/android/aitt/handler/TransportHandler.java index 2015c4a..7955d3d 100644 --- a/android/aitt/src/main/java/com/samsung/android/aitt/handler/TransportHandler.java +++ b/android/aitt/src/main/java/com/samsung/android/aitt/handler/TransportHandler.java @@ -60,11 +60,4 @@ public interface TransportHandler extends ModuleHandler { * @return returns details of self device */ byte[] getPublishData(); - - /** - * Interface to implement handler data callback mechanism - */ - interface HandlerDataCallback { - void pushHandlerData(byte[] data); - } } diff --git a/android/aitt/src/main/java/com/samsung/android/aitt/handler/WebRTCHandler.java b/android/aitt/src/main/java/com/samsung/android/aitt/handler/WebRTCHandler.java index 36960bd..e2382f5 100644 --- a/android/aitt/src/main/java/com/samsung/android/aitt/handler/WebRTCHandler.java +++ b/android/aitt/src/main/java/com/samsung/android/aitt/handler/WebRTCHandler.java @@ -15,105 +15,40 @@ */ package com.samsung.android.aitt.handler; +import static com.samsung.android.aitt.stream.WebRTCStream.createPublisherStream; +import static com.samsung.android.aitt.stream.WebRTCStream.createSubscriberStream; + import android.content.Context; +import android.util.Log; -import com.google.flatbuffers.FlexBuffersBuilder; import com.samsung.android.aitt.Aitt; -import com.samsung.android.aitt.Definitions; -import com.samsung.android.modules.webrtc.WebRTC; -import com.samsung.android.modules.webrtc.WebRTCServer; - -import java.nio.ByteBuffer; - -public class WebRTCHandler implements TransportHandler { +import com.samsung.android.aitt.stream.AittStream; - private Context appContext; - private String ip; - private byte[] publishData; - private WebRTC webrtc; - private WebRTCServer ws; - //ToDo - For now using sample app parameters, later fetch frameWidth & frameHeight from app - private final Integer frameWidth = 640; - private final Integer frameHeight = 480; +import java.security.InvalidParameterException; - public WebRTCHandler() { - //ToDo : Copy jni interface and use to communicate with JNI - } - - @Override - public void setAppContext(Context appContext) { - this.appContext = appContext; - } +public final class WebRTCHandler extends StreamHandler { - @Override - public void setSelfIP(String ip) { - this.ip = ip; - } + private static final String TAG = "WebRTCHandler"; @Override - public void subscribe(String topic, HandlerDataCallback handlerDataCallback) { - WebRTC.ReceiveDataCallback cb = handlerDataCallback::pushHandlerData; - ws = new WebRTCServer(appContext, cb); - int serverPort = ws.start(); - if (serverPort < 0) { - throw new IllegalArgumentException("Failed to start webRTC server-socket"); - } - - publishData = wrapPublishData(topic, serverPort); + public void setAppContext(Context context) { } @Override - public byte[] getPublishData() { - return publishData; - } - - /** - * Method to wrap topic, device IP address, webRTC server instance port number for publishing - * - * @param topic Topic to which the application has subscribed to - * @param serverPort Port number of the WebRTC server instance - * @return Byte data wrapped, contains topic, device IP, webRTC server port number - */ - private byte[] wrapPublishData(String topic, int serverPort) { - FlexBuffersBuilder fbb = new FlexBuffersBuilder(ByteBuffer.allocate(512)); - { - int smap = fbb.startMap(); - fbb.putString(Definitions.STATUS, Definitions.JOIN_NETWORK); - fbb.putString("host", ip); - { - int smap1 = fbb.startMap(); - fbb.putInt("protocol", Aitt.Protocol.WEBRTC.getValue()); - fbb.putInt("port", serverPort); - fbb.endMap(topic, smap1); + public AittStream newStreamModule(Aitt.Protocol protocol, String topic, AittStream.StreamRole role, Context context) { + if (protocol != Aitt.Protocol.WEBRTC) + throw new InvalidParameterException("Invalid protocol"); + + try { + if (role == AittStream.StreamRole.SUBSCRIBER) { + return createSubscriberStream(topic, role, context); + } else { + return createPublisherStream(topic, role, context); } - fbb.endMap(null, smap); - } - ByteBuffer buffer = fbb.finish(); - byte[] data = new byte[buffer.remaining()]; - buffer.get(data, 0, data.length); - return data; - } - - @Override - public void publish(String topic, String ip, int port, byte[] message) { - if (webrtc == null) { - webrtc = new WebRTC(appContext); - webrtc.connect(ip, port); - } - if (topic.endsWith(Definitions.RESPONSE_POSTFIX)) { - webrtc.sendMessageData(message); - } else { - webrtc.sendVideoData(message, frameWidth, frameHeight); + } catch (Exception e) { + Log.e(TAG, "Fail to create an AittStream instance."); } - } - - @Override - public void unsubscribe() { - ws.stop(); - } - - @Override - public void disconnect() { + return null; } } diff --git a/android/aitt/src/main/java/com/samsung/android/aitt/stream/AittStream.java b/android/aitt/src/main/java/com/samsung/android/aitt/stream/AittStream.java index 9ad96ab..e7b08f0 100644 --- a/android/aitt/src/main/java/com/samsung/android/aitt/stream/AittStream.java +++ b/android/aitt/src/main/java/com/samsung/android/aitt/stream/AittStream.java @@ -15,6 +15,8 @@ */ package com.samsung.android.aitt.stream; +import com.samsung.android.aitt.handler.ModuleHandler; + public interface AittStream { enum StreamRole { @@ -22,13 +24,23 @@ public interface AittStream { SUBSCRIBER } + enum StreamState { + INIT, + READY, + PLAYING + } + void setConfig(); void start(); + boolean publish(String topic, String ip, int port, byte[] message); + + void disconnect(); + void stop(); void setStateCallback(); - void setReceiveCallback(); + void setReceiveCallback(ModuleHandler.HandlerDataCallback handlerDataCallback); } diff --git a/android/aitt/src/main/java/com/samsung/android/aitt/stream/RTSPStream.java b/android/aitt/src/main/java/com/samsung/android/aitt/stream/RTSPStream.java index 199913f..7044a4b 100644 --- a/android/aitt/src/main/java/com/samsung/android/aitt/stream/RTSPStream.java +++ b/android/aitt/src/main/java/com/samsung/android/aitt/stream/RTSPStream.java @@ -15,6 +15,8 @@ */ package com.samsung.android.aitt.stream; +import com.samsung.android.aitt.handler.ModuleHandler; + public class RTSPStream implements AittStream { @Override @@ -28,6 +30,17 @@ public class RTSPStream implements AittStream { } @Override + public boolean publish(String topic, String ip, int port, byte[] message) { + // TODO: implement this function. + return true; + } + + @Override + public void disconnect() { + // TODO: implement this function. + } + + @Override public void stop() { // TODO: implement this function. } @@ -46,7 +59,7 @@ public class RTSPStream implements AittStream { } @Override - public void setReceiveCallback() { + public void setReceiveCallback(ModuleHandler.HandlerDataCallback handlerDataCallback) { // TODO: implement this function. } } diff --git a/android/aitt/src/main/java/com/samsung/android/aitt/stream/WebRTCStream.java b/android/aitt/src/main/java/com/samsung/android/aitt/stream/WebRTCStream.java new file mode 100644 index 0000000..db9d396 --- /dev/null +++ b/android/aitt/src/main/java/com/samsung/android/aitt/stream/WebRTCStream.java @@ -0,0 +1,171 @@ +package com.samsung.android.aitt.stream; + +import android.content.Context; +import android.util.Log; + +import com.google.flatbuffers.FlexBuffersBuilder; +import com.samsung.android.aitt.Aitt; +import com.samsung.android.aitt.Definitions; +import com.samsung.android.aitt.handler.ModuleHandler; +import com.samsung.android.aittnative.JniInterface; +import com.samsung.android.modules.webrtc.WebRTC; +import com.samsung.android.modules.webrtc.WebRTCServer; + +import java.nio.ByteBuffer; + +public final class WebRTCStream implements AittStream { + + private static final String TAG = "WebRTCStream"; + + private final String topic; + private final StreamRole streamRole; + + private int serverPort; + private String ip; + private JniInterface jniInterface; + private WebRTC webrtc; + private WebRTCServer ws = null; + private StreamState streamState = StreamState.INIT; + + WebRTCStream(String topic, StreamRole streamRole, Context context) { + this.topic = topic; + this.streamRole = streamRole; + + if (streamRole == StreamRole.PUBLISHER) + webrtc = new WebRTC(context); // TODO: Currently, native stream servers are publishers. + else + ws = new WebRTCServer(context); // TODO: Currently, native stream clients are subscribers. + } + + public static WebRTCStream createSubscriberStream(String topic, StreamRole streamRole, Context context) { + if (streamRole != StreamRole.SUBSCRIBER) + throw new IllegalArgumentException("The role of this stream is not subscriber."); + + return new WebRTCStream(topic, streamRole, context); + } + + public static WebRTCStream createPublisherStream(String topic, StreamRole streamRole, Context context) { + if (streamRole != StreamRole.PUBLISHER) + throw new IllegalArgumentException("The role of this stream is not publisher."); + + return new WebRTCStream(topic, streamRole, context); + } + + @Override + public void setConfig() { + + } + + @Override + public void start() { + if (streamRole == StreamRole.SUBSCRIBER) { + serverPort = ws.start(); + if (serverPort < 0) + throw new RuntimeException("Failed to start a WebRTC server socket."); + Log.d(TAG, "Start a WebRTC Server, port = " + serverPort); + + byte[] publishData = wrapPublishData(topic, serverPort); + jniInterface.publish(Definitions.JAVA_SPECIFIC_DISCOVERY_TOPIC, publishData, publishData.length, Aitt.Protocol.MQTT.getValue(), Aitt.QoS.EXACTLY_ONCE.ordinal(), true); + } + } + + @Override + public void disconnect() { + if (webrtc != null) + webrtc.disconnect(); + } + + @Override + public void stop() { + if (ws != null) + ws.stop(); + } + + @Override + public void setStateCallback() { + + } + + @Override + public void setReceiveCallback(ModuleHandler.HandlerDataCallback handlerDataCallback) { + if (handlerDataCallback == null) + throw new IllegalArgumentException("The given callback is null."); + + if (streamRole == StreamRole.SUBSCRIBER) { + WebRTC.ReceiveDataCallback cb = handlerDataCallback::pushHandlerData; + ws.setDataCallback(cb); + } else if (streamRole == StreamRole.PUBLISHER) { + Log.e(TAG, "Invalid function call"); + } + } + + public int getServerPort() { + return serverPort; + } + + public void setSelfIP(String ip) { + this.ip = ip; + } + + /** + * Method to wrap topic, device IP address, and the port number of a webRTC server instance for publishing + * + * @param topic Topic to which the application has subscribed to + * @param serverPort Port number of the WebRTC server instance + * @return Byte data wrapped, contains topic, device IP, webRTC server port number + */ + private byte[] wrapPublishData(String topic, int serverPort) { + FlexBuffersBuilder fbb = new FlexBuffersBuilder(ByteBuffer.allocate(512)); + { + int smap = fbb.startMap(); + fbb.putString(Definitions.STATUS, Definitions.JOIN_NETWORK); + fbb.putString("host", ip); + { + int smap1 = fbb.startMap(); + fbb.putInt("protocol", Aitt.Protocol.WEBRTC.getValue()); + fbb.putInt("port", serverPort); + fbb.endMap(topic, smap1); + } + fbb.endMap(null, smap); + } + ByteBuffer buffer = fbb.finish(); + byte[] data = new byte[buffer.remaining()]; + buffer.get(data, 0, data.length); + return data; + } + + @Override + public boolean publish(String topic, String ip, int port, byte[] message) { + if (streamRole == StreamRole.PUBLISHER) { + if (webrtc == null) { + Log.e(TAG, "A WebRTC instance is null."); + return false; + } + + if (streamState == StreamState.INIT) { + webrtc.connect(ip, port); + streamState = StreamState.READY; + Log.d(TAG, "A WebRTC client is connected into a server."); + } + + if (topic.endsWith(Definitions.RESPONSE_POSTFIX)) { + Log.d(TAG, "A message is sent through a WebRTC publisher stream."); + return webrtc.sendMessageData(message); + } else { + Log.d(TAG, "Video data are sent through a WebRTC publisher stream."); + //ToDo - For now using sample app parameters, later fetch frameWidth & frameHeight from app + int frameWidth = 640; + int frameHeight = 480; + webrtc.sendVideoData(message, frameWidth, frameHeight); + } + } else { + Log.e(TAG, "publish() is not allowed to a subscriber stream."); + } + + return true; + } + + public void setJNIInterface(JniInterface jniInterface) { + this.jniInterface = jniInterface; + } +} diff --git a/android/modules/rtsp/src/androidTest/java/com/samsung/android/modules/rtsp/RTSPInstrumentedTest.java b/android/modules/rtsp/src/androidTest/java/com/samsung/android/modules/rtsp/RTSPInstrumentedTest.java index cd2d6bc..8e638d1 100644 --- a/android/modules/rtsp/src/androidTest/java/com/samsung/android/modules/rtsp/RTSPInstrumentedTest.java +++ b/android/modules/rtsp/src/androidTest/java/com/samsung/android/modules/rtsp/RTSPInstrumentedTest.java @@ -54,7 +54,7 @@ public class RTSPInstrumentedTest { aitt.connect(brokerIp, PORT); AittStream subscriber = aitt.createStream(Aitt.Protocol.RTSP, TEST_TOPIC, SUBSCRIBER); - subscriber.setReceiveCallback(/* TODO */); + subscriber.setReceiveCallback(/* TODO */null); subscriber.start(); AittStream publisher = aitt.createStream(Aitt.Protocol.RTSP, TEST_TOPIC, PUBLISHER); diff --git a/android/modules/webrtc/build.gradle b/android/modules/webrtc/build.gradle index 79d9dc7..e278215 100644 --- a/android/modules/webrtc/build.gradle +++ b/android/modules/webrtc/build.gradle @@ -6,12 +6,11 @@ android { compileSdkVersion 31 defaultConfig { - minSdkVersion 21 + minSdkVersion 28 targetSdkVersion 31 - versionCode 1 - versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles "consumer-rules.pro" } buildTypes { release { @@ -27,12 +26,11 @@ android { dependencies { implementation 'androidx.appcompat:appcompat:1.4.1' + implementation 'com.google.android.material:material:1.4.0' implementation 'org.webrtc:google-webrtc:1.0.32006' - testImplementation 'junit:junit:4.+' + + androidTestImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' -} - -task wrapper(type: Wrapper) { - gradleVersion = '4.1' + androidTestImplementation project(path: ':android:aitt') } diff --git a/android/modules/webrtc/src/androidTest/java/com/samsung/android/modules/webrtc/WebRTCInstrumentedTest.java b/android/modules/webrtc/src/androidTest/java/com/samsung/android/modules/webrtc/WebRTCInstrumentedTest.java new file mode 100644 index 0000000..08dd397 --- /dev/null +++ b/android/modules/webrtc/src/androidTest/java/com/samsung/android/modules/webrtc/WebRTCInstrumentedTest.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2022 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. + */ +package com.samsung.android.modules.webrtc; + +import static com.samsung.android.aitt.stream.AittStream.StreamRole.PUBLISHER; +import static com.samsung.android.aitt.stream.AittStream.StreamRole.SUBSCRIBER; +import static org.junit.Assert.fail; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.LinkAddress; +import android.net.LinkProperties; +import android.net.Network; +import android.os.Looper; +import android.util.Log; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.samsung.android.aitt.Aitt; +import com.samsung.android.aitt.stream.AittStream; +import com.samsung.android.aitt.stream.WebRTCStream; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.util.Arrays; +import java.util.StringTokenizer; + +public class WebRTCInstrumentedTest { + + private static final String TEST_TOPIC = "android/test/webrtc/_AittRe_"; + private static final String AITT_WEBRTC_SERVER_ID = "AITT_ANDROID_WEBRTC_SERVER"; + private static final String AITT_WEBRTC_CLIENT_ID = "AITT_ANDROID_WEBRTC_CLIENT"; + private static final String TAG = "WebRTCInstrumentedTest"; + private static final int PORT = 1883; + private static final int SLEEP_INTERVAL = 100; + + private static String brokerIp; + private static Context appContext; + private static Looper looper; + + @BeforeClass + public static void initialize() { + appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + // IMPORTANT NOTE: Should give test arguments as follows. + // if using Android studio: Run -> Edit Configurations -> Find 'Instrumentation arguments' + // -> press '...' button -> add the name as "brokerIp" and the value + // (Broker WiFi IP) of broker argument + // if using gradlew commands: Add "-e brokerIp [Broker WiFi IP]" + brokerIp = InstrumentationRegistry.getArguments().getString("brokerIp"); + + if (Looper.myLooper() == null) { + Looper.prepare(); + looper = Looper.myLooper(); + Log.i(TAG, "ALooper is prepared in this test case."); + } + } + + @Test + public void testWebRTCBasicStreaming() { + String message = "Hello, WebRTC!!"; + + try { + String ip = wifiIpAddress(); + + Aitt serverSubscriber = new Aitt(appContext, AITT_WEBRTC_SERVER_ID, ip, true); + serverSubscriber.connect(brokerIp, PORT); + AittStream serverSubscriberStream = serverSubscriber.createStream(Aitt.Protocol.WEBRTC, TEST_TOPIC, SUBSCRIBER); + serverSubscriberStream.setReceiveCallback(data -> { + Log.i(TAG, "Callback is received."); + if (Arrays.equals(data, message.getBytes()) == false) + throw new RuntimeException("A wrong test message(" + new String(data) + ") is received."); + + Log.i(TAG, "The correct test message(" + new String(data) + ") is received."); + if (looper != null) + looper.quit(); + }); + Log.i(TAG, "A WebRTC server and a subscriber stream is created."); + + Aitt clientPublisher = new Aitt(appContext, AITT_WEBRTC_CLIENT_ID, ip, true); + clientPublisher.connect(brokerIp, PORT); + AittStream clientPublisherStream = clientPublisher.createStream(Aitt.Protocol.WEBRTC, TEST_TOPIC, PUBLISHER); + clientPublisherStream.start(); + Log.i(TAG, "A WebRTC client and a publisher stream is created."); + + serverSubscriberStream.start(); + Log.i(TAG, "The subscriber stream starts."); + + int intervalSum = 0; + while (intervalSum < 1000) { + Thread.sleep(SLEEP_INTERVAL); + intervalSum += SLEEP_INTERVAL; + } + + int serverPort = ((WebRTCStream) serverSubscriberStream).getServerPort(); + Log.d(TAG, "Server port = " + serverPort); + while (true) { + // TODO: Replace publish + boolean isPublished = clientPublisher.publish(clientPublisherStream, TEST_TOPIC, message.getBytes(), Aitt.Protocol.WEBRTC); + if (isPublished) + break; + } + Log.i(TAG, "A message is sent through a publisher stream."); + + Looper.loop(); + Log.i(TAG, "A looper is finished."); + } catch (Exception e) { + fail("Failed testWebRTCBasicStreaming, (" + e + ")"); + } + } + + private static String wifiIpAddress() { + ConnectivityManager connectivityManager = (ConnectivityManager) appContext.getSystemService(Context.CONNECTIVITY_SERVICE); + Network currentNetwork = connectivityManager.getActiveNetwork(); + LinkProperties linkProperties = connectivityManager.getLinkProperties(currentNetwork); + for (LinkAddress linkAddress : linkProperties.getLinkAddresses()) { + String targetIp = linkAddress.toString(); + if (targetIp.contains("192.168")) { + StringTokenizer tokenizer = new StringTokenizer(targetIp, "/"); + if (tokenizer.hasMoreTokens()) + return tokenizer.nextToken(); + } + } + + return null; + } +} diff --git a/android/modules/webrtc/src/main/AndroidManifest.xml b/android/modules/webrtc/src/main/AndroidManifest.xml index 7b7ae3f..44e8f2e 100644 --- a/android/modules/webrtc/src/main/AndroidManifest.xml +++ b/android/modules/webrtc/src/main/AndroidManifest.xml @@ -4,5 +4,5 @@ - + diff --git a/android/modules/webrtc/src/main/java/com/samsung/android/modules/webrtc/WebRTC.java b/android/modules/webrtc/src/main/java/com/samsung/android/modules/webrtc/WebRTC.java index a73017d..d7020dd 100644 --- a/android/modules/webrtc/src/main/java/com/samsung/android/modules/webrtc/WebRTC.java +++ b/android/modules/webrtc/src/main/java/com/samsung/android/modules/webrtc/WebRTC.java @@ -65,14 +65,18 @@ import java.util.concurrent.TimeUnit; /** * WebRTC class to implement webRTC functionalities */ -public class WebRTC { +public final class WebRTC { + public static final String VIDEO_TRACK_ID = "ARDAMSv0"; public static final String EOF_MESSAGE = "EOF"; public static final int MAX_MESSAGE_SIZE = 32768; + private static final String TAG = "WebRTC"; private static final String CANDIDATE = "candidate"; + private final Context appContext; private final boolean isReceiver; + private java.net.Socket socket; private boolean isInitiator; private boolean isChannelReady; @@ -421,8 +425,7 @@ public class WebRTC { buffer.data.rewind(); buffer.data.get(array); dataCallback.pushData(array); - } - else { + } else { String message = StandardCharsets.UTF_8.decode(buffer.data).toString(); handlelargeMessage(message, buffer); } @@ -485,11 +488,10 @@ public class WebRTC { * * @param message message to be sent in byte format */ - public void sendMessageData(byte[] message) { - if (message.length < MAX_MESSAGE_SIZE ) { + public boolean sendMessageData(byte[] message) { + if (message.length < MAX_MESSAGE_SIZE) { ByteBuffer data = ByteBuffer.wrap(message); - localDataChannel.send(new DataChannel.Buffer(data, false)); - return; + return localDataChannel.send(new DataChannel.Buffer(data, false)); } ByteBuffer chunkData; @@ -504,7 +506,7 @@ public class WebRTC { } chunkData = ByteBuffer.wrap(EOF_MESSAGE.getBytes(StandardCharsets.UTF_8)); - localDataChannel.send(new DataChannel.Buffer(chunkData, false)); + return localDataChannel.send(new DataChannel.Buffer(chunkData, false)); } /** diff --git a/android/modules/webrtc/src/main/java/com/samsung/android/modules/webrtc/WebRTCServer.java b/android/modules/webrtc/src/main/java/com/samsung/android/modules/webrtc/WebRTCServer.java index 982580d..b1dfd48 100644 --- a/android/modules/webrtc/src/main/java/com/samsung/android/modules/webrtc/WebRTCServer.java +++ b/android/modules/webrtc/src/main/java/com/samsung/android/modules/webrtc/WebRTCServer.java @@ -27,11 +27,14 @@ import java.util.List; /** * Class to implement WebRTC server related functionalities */ -public class WebRTCServer { +public final class WebRTCServer { + private static final String TAG = "WebRTCServer"; + private final Context appContext; - private final WebRTC.ReceiveDataCallback dataCallback; private final List connectionList = new ArrayList<>(); + + private WebRTC.ReceiveDataCallback dataCallback; private ServerSocket serverSocket = null; private ServerThread serverThread = null; private Thread thread; @@ -39,11 +42,18 @@ public class WebRTCServer { /** * WebRTCServer constructor to create its instance * - * @param appContext Application context of the app creating WebRTCServer instance - * @param dataCallback Data callback object to create call back mechanism + * @param appContext Application context of the app creating WebRTCServer instance */ - public WebRTCServer(Context appContext, WebRTC.ReceiveDataCallback dataCallback) { + public WebRTCServer(Context appContext) { this.appContext = appContext; + } + + /** + * Setter to set a WebRTC ReceiveDataCallback + * + * @param dataCallback Data callback object to create call back mechanism + */ + public void setDataCallback(WebRTC.ReceiveDataCallback dataCallback) { this.dataCallback = dataCallback; } @@ -53,6 +63,11 @@ public class WebRTCServer { * @return Returns Port number on success and -1 on failure */ public int start() { + if (dataCallback == null) { + Log.e(TAG, "Data callback is null."); + return -1; + } + try { serverSocket = new ServerSocket(0); } catch (IOException e) { diff --git a/android/modules/webrtc/src/test/java/com/samsung/android/modules/webrtc/ExampleUnitTest.java b/android/modules/webrtc/src/test/java/com/samsung/android/modules/webrtc/ExampleUnitTest.java deleted file mode 100644 index 1536b3d..0000000 --- a/android/modules/webrtc/src/test/java/com/samsung/android/modules/webrtc/ExampleUnitTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.samsung.android.modules.webrtc; - -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Example local unit test, which will execute on the development machine (host). - * - * @see Testing documentation - */ -public class ExampleUnitTest { - @Test - public void addition_isCorrect() { - assertEquals(4, 2 + 2); - } -} \ No newline at end of file -- 2.7.4