aardvark-fan
aardvark-fan

Reputation: 95

Can WebRTC work over an Android USB Ethernet connection?

I am trying to use WebRTC on an Android App to stream to a media server running on my workstation using a USB tethering connection. I see have good signaling, with an offer and an answer that seem to make sense with respect to the IP addresses, but I do not get any UDP streaming traffic.

Is there something in the Android WebRtc library that would omit this rndis0 interface? Over Wi-Fi, everything works as intended, but not if the tethered ethernet is the only connection.

Upvotes: 1

Views: 378

Answers (1)

mwarning
mwarning

Reputation: 771

Not sure if this is the same problem, but WebRTC does not find tethered interfaces on Android. This was working for me https://groups.google.com/g/discuss-webrtc/c/pTWe0QLKNC4/m/_ulh_2NJBwAJ

Here is a patch for WebRTC, Tested with WebRTC M123 and Android 10:

diff --git a/sdk/android/api/org/webrtc/NetworkMonitorAutoDetect.java b/sdk/android/api/org/webrtc/NetworkMonitorAutoDetect.java
index a6f24c2858..af21c775b3 100644
--- a/sdk/android/api/org/webrtc/NetworkMonitorAutoDetect.java
+++ b/sdk/android/api/org/webrtc/NetworkMonitorAutoDetect.java
@@ -38,8 +38,10 @@ import java.net.NetworkInterface;
 import java.net.SocketException;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -622,6 +624,146 @@ public class NetworkMonitorAutoDetect extends BroadcastReceiver implements Netwo
     }
   }
 
+  static class TetheringDelegate extends BroadcastReceiver {
+    private static final String EXTRA_ACTIVE_TETHER =
+        android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? "tetherArray" : "activeArray";
+    private static final String EXTRA_ERRORED_TETHER = "erroredArray";
+    private static final String EXTRA_AVAILABLE_TETHER = "availableArray";
+    private static final String ACTION_TETHER_STATE_CHANGED = "android.net.conn.TETHER_STATE_CHANGED";
+
+    private static final int TETHERING_NETWORK_HANDLE = 0;
+
+    private final Context context;
+    private final NetworkChangeDetector.Observer observer;
+    private List<NetworkInformation> tetheringNetworkInfo;
+
+    private static class NetworkInformationDiff {
+        public List<String> added = new ArrayList<>();
+        public List<NetworkInformation> unChanged = new ArrayList<>();
+        public List<NetworkInformation> removed = new ArrayList<>();
+
+        public boolean isNoChange() {
+            return added.isEmpty() && removed.isEmpty();
+        }
+
+        private List<String> getIntfNames(List<NetworkInformation> netInfos) {
+            List<String> intfNames = new ArrayList<>();
+            for (NetworkInformation netInfo : netInfos) {
+                intfNames.add(netInfo.name);
+            }
+            return intfNames;
+        }
+    }
+
+    TetheringDelegate(NetworkChangeDetector.Observer observer, Context context) {
+        this.context = context;
+        this.observer = observer;
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(ACTION_TETHER_STATE_CHANGED);
+        context.registerReceiver(this, intentFilter);
+    }
+
+    @Override
+    @SuppressLint("InlinedApi")
+    public void onReceive(Context context, Intent intent) {
+        if (ACTION_TETHER_STATE_CHANGED.equals(intent.getAction())) {
+            List<String> tetherIfNames = intent.getStringArrayListExtra(EXTRA_ACTIVE_TETHER);
+            List<String> erroredIfNames = intent.getStringArrayListExtra(EXTRA_ERRORED_TETHER);
+            List<String> availableIfNames = intent.getStringArrayListExtra(EXTRA_AVAILABLE_TETHER);
+            onTetheringStateChange(tetherIfNames, erroredIfNames, availableIfNames);
+        }
+    }
+
+    public void release() {
+        context.unregisterReceiver(this);
+    }
+
+    public List<NetworkInformation> getActiveNetworkList() {
+        return tetheringNetworkInfo != null ? tetheringNetworkInfo : Collections.emptyList();
+    }
+
+    private NetworkInformationDiff computeNetworkInfoDiff(List<String> newNetIntfs) {
+        Map<String, NetworkInformation> oldMap = new HashMap<>();
+        NetworkInformationDiff diff = new NetworkInformationDiff();
+        Set<String> newNetInfsSet = new HashSet<>(newNetIntfs);
+
+        if (tetheringNetworkInfo != null) {
+            for (NetworkInformation oldNetInfo : tetheringNetworkInfo) {
+                oldMap.put(oldNetInfo.name, oldNetInfo);
+            }
+        }
+
+        for (String oldNetInfoName : oldMap.keySet()) {
+            newNetInfsSet.remove(oldNetInfoName);
+        }
+
+        for (String newNetInfoName : newNetInfsSet) {
+            NetworkInformation unChanged = oldMap.remove(newNetInfoName);
+            if (unChanged != null) {
+                diff.unChanged.add(unChanged);
+            }
+        }
+
+        diff.added.addAll(newNetInfsSet);
+        diff.removed.addAll(oldMap.values());
+
+        return diff;
+    }
+
+    private void onTetheringStateChange(List<String> tetherIfNames,
+                                        List<String> erroredIfNames,
+                                        List<String> availableIfNames) {
+        //Logging.d(TAG, "onTetheringStateChange: "
+        //    + "tetherIfNames: " + String.join(", ", tetherIfNames)
+        //    + ", erroredIfNames: " + String.join(", ", erroredIfNames)
+        //    + ", availableIfNames: " + String.join(", ", availableIfNames));
+
+        NetworkInformationDiff diff = computeNetworkInfoDiff(tetherIfNames);
+
+        if (diff.isNoChange()) {
+            return;
+        }
+
+        List<NetworkInformation> netInfos = new ArrayList<>(diff.unChanged);
+        List<NetworkInformation> newNetInfos = new ArrayList<>();
+        for (String ifName : diff.added) {
+          try {
+            NetworkInterface intf = NetworkInterface.getByName(ifName);
+            if (intf == null) {
+                continue;
+            }
+
+            List<InetAddress> interfaceAddresses = Collections.list(intf.getInetAddresses());
+            IPAddress[] ipAddresses = new IPAddress[interfaceAddresses.size()];
+            for (int i = 0; i < interfaceAddresses.size(); ++i) {
+                InetAddress inetAddress = interfaceAddresses.get(i);
+                ipAddresses[i] = new IPAddress(inetAddress.getAddress());
+            }
+
+            NetworkInformation netInfo = new NetworkInformation(ifName,
+                    NetworkChangeDetector.ConnectionType.CONNECTION_ETHERNET,
+                    NetworkChangeDetector.ConnectionType.CONNECTION_NONE, TETHERING_NETWORK_HANDLE,
+                    ipAddresses);
+
+            newNetInfos.add(netInfo);
+          } catch (Exception e) {
+            Logging.e(TAG, "onTetheringStateChange() " + e.toString());
+          }
+        }
+
+        netInfos.addAll(newNetInfos);
+        tetheringNetworkInfo = netInfos;
+
+        for (NetworkInformation connNetInfo : newNetInfos) {
+            observer.onNetworkConnect(connNetInfo);
+        }
+
+        for (NetworkInformation discNetInfo : diff.removed) {
+            observer.onNetworkDisconnect(discNetInfo.handle);
+        }
+      }
+  }
+
   private static final long INVALID_NET_ID = -1;
   private static final String TAG = "NetworkMonitorAutoDetect";
 
@@ -638,6 +780,7 @@ public class NetworkMonitorAutoDetect extends BroadcastReceiver implements Netwo
   private ConnectivityManagerDelegate connectivityManagerDelegate;
   private WifiManagerDelegate wifiManagerDelegate;
   private WifiDirectManagerDelegate wifiDirectManagerDelegate;
+  private TetheringDelegate tetheringDelegate;
   private static boolean includeWifiDirect;
 
   @GuardedBy("availableNetworks") final Set<Network> availableNetworks = new HashSet<>();
@@ -664,6 +807,7 @@ public class NetworkMonitorAutoDetect extends BroadcastReceiver implements Netwo
     if (includeWifiDirect) {
       wifiDirectManagerDelegate = new WifiDirectManagerDelegate(observer, context);
     }
+    tetheringDelegate = new TetheringDelegate(observer, context);
 
     registerReceiver();
     if (connectivityManagerDelegate.supportNetworkCallback()) {
@@ -730,6 +874,9 @@ public class NetworkMonitorAutoDetect extends BroadcastReceiver implements Netwo
     if (wifiDirectManagerDelegate != null) {
       result.addAll(wifiDirectManagerDelegate.getActiveNetworkList());
     }
+    if (tetheringDelegate != null) {
+      result.addAll(tetheringDelegate.getActiveNetworkList());
+    }
     return result;
   }
 
@@ -744,6 +891,9 @@ public class NetworkMonitorAutoDetect extends BroadcastReceiver implements Netwo
     if (wifiDirectManagerDelegate != null) {
       wifiDirectManagerDelegate.release();
     }
+    if (tetheringDelegate != null) {
+      tetheringDelegate.release();
+    }
     unregisterReceiver();
   }
 
-- 
2.45.2

Another possible solution might be to simply disable the WebRTC NetworkMonitor:

val options = PeerConnectionFactory.Options()
options.disableNetworkMonitor = true

factory = PeerConnectionFactory.builder()
         .setOptions(options)
         .setAudioDeviceModule(adm)
         .setVideoEncoderFactory(encoderFactory)
         .setVideoDecoderFactory(decoderFactory)

Upvotes: 1

Related Questions