MagicWebRTCUtils.java 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. /*
  2. * Nextcloud Talk application
  3. *
  4. * @author Mario Danic
  5. * Copyright (C) 2017 Mario Danic <mario@lovelyhq.com>
  6. *
  7. * This program is free software: you can redistribute it and/or modify
  8. * it under the terms of the GNU General Public License as published by
  9. * the Free Software Foundation, either version 3 of the License, or
  10. * at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  19. *
  20. * Original code:
  21. *
  22. *
  23. * Copyright 2016 The WebRTC Project Authors. All rights reserved.
  24. *
  25. * Use of this source code is governed by a BSD-style license
  26. * that can be found in the LICENSE file in the root of the source
  27. * tree. An additional intellectual property rights grant can be found
  28. * in the file PATENTS. All contributing project authors may
  29. * be found in the AUTHORS file in the root of the source tree.
  30. */
  31. package com.nextcloud.talk.webrtc;
  32. import android.os.Build;
  33. import android.util.Log;
  34. import java.util.ArrayList;
  35. import java.util.Arrays;
  36. import java.util.HashSet;
  37. import java.util.Iterator;
  38. import java.util.List;
  39. import java.util.Set;
  40. import java.util.regex.Matcher;
  41. import java.util.regex.Pattern;
  42. public class MagicWebRTCUtils {
  43. private static final String TAG = "MagicWebRTCUtils";
  44. /* AEC blacklist and SL_ES_WHITELIST are borrowed from Signal
  45. https://github.com/WhisperSystems/Signal-Android/blob/551470123d006b76a68d705d131bb12513a5e683/src/org/thoughtcrime/securesms/ApplicationContext.java
  46. */
  47. public static Set<String> HARDWARE_AEC_BLACKLIST = new HashSet<String>() {{
  48. add("D6503"); // Sony Xperia Z2 D6503
  49. add("ONE A2005"); // OnePlus 2
  50. add("MotoG3"); // Moto G (3rd Generation)
  51. add("Nexus 6P"); // Nexus 6p
  52. add("Pixel"); // Pixel
  53. add("Pixel XL"); // Pixel XL
  54. add("MI 4LTE"); // Xiami Mi4
  55. add("Redmi Note 3"); // Redmi Note 3
  56. add("Redmi Note 4"); // Redmi Note 4
  57. add("SM-G900F"); // Samsung Galaxy S5
  58. add("g3_kt_kr"); // LG G3
  59. add("SM-G930F"); // Samsung Galaxy S7
  60. add("Xperia SP"); // Sony Xperia SP
  61. add("Nexus 6"); // Nexus 6
  62. add("ONE E1003"); // OnePlus X
  63. add("One"); // OnePlus One
  64. add("Moto G5");
  65. }};
  66. public static Set<String> OPEN_SL_ES_WHITELIST = new HashSet<String>() {{
  67. add("Pixel");
  68. add("Pixel XL");
  69. }};
  70. private static Set<String> HARDWARE_ACCELERATION_DEVICE_BLACKLIST = new HashSet<String>() {{
  71. add("GT-I9100"); // Samsung Galaxy S2
  72. add("GT-N8013"); // Samsung Galaxy Note 10.1
  73. add("SM-G930F"); // Samsung Galaxy S7
  74. add("AGS-W09"); // Huawei MediaPad T3 10
  75. }};
  76. private static Set<String> HARDWARE_ACCELERATION_VENDOR_BLACKLIST = new HashSet<String>() {{
  77. add("samsung");
  78. }};
  79. public static boolean shouldEnableVideoHardwareAcceleration() {
  80. return (!HARDWARE_ACCELERATION_VENDOR_BLACKLIST.contains(Build.MANUFACTURER.toLowerCase())
  81. && !HARDWARE_ACCELERATION_DEVICE_BLACKLIST.contains(Build.MODEL));
  82. }
  83. public static String preferCodec(String sdpDescription, String codec, boolean isAudio) {
  84. final String[] lines = sdpDescription.split("\r\n");
  85. final int mLineIndex = findMediaDescriptionLine(isAudio, lines);
  86. if (mLineIndex == -1) {
  87. Log.w(TAG, "No mediaDescription line, so can't prefer " + codec);
  88. return sdpDescription;
  89. }
  90. // A list with all the payload types with name |codec|. The payload types are integers in the
  91. // range 96-127, but they are stored as strings here.
  92. final List<String> codecPayloadTypes = new ArrayList<String>();
  93. // a=rtpmap:<payload type> <encoding name>/<clock rate> [/<encoding parameters>]
  94. final Pattern codecPattern = Pattern.compile("^a=rtpmap:(\\d+) " + codec + "(/\\d+)+[\r]?$");
  95. for (int i = 0; i < lines.length; ++i) {
  96. Matcher codecMatcher = codecPattern.matcher(lines[i]);
  97. if (codecMatcher.matches()) {
  98. codecPayloadTypes.add(codecMatcher.group(1));
  99. }
  100. }
  101. if (codecPayloadTypes.isEmpty()) {
  102. Log.w(TAG, "No payload types with name " + codec);
  103. return sdpDescription;
  104. }
  105. final String newMLine = movePayloadTypesToFront(codecPayloadTypes, lines[mLineIndex]);
  106. if (newMLine == null) {
  107. return sdpDescription;
  108. }
  109. Log.d(TAG, "Change media description from: " + lines[mLineIndex] + " to " + newMLine);
  110. lines[mLineIndex] = newMLine;
  111. return joinString(Arrays.asList(lines), "\r\n", true /* delimiterAtEnd */);
  112. }
  113. /**
  114. * Returns the line number containing "m=audio|video", or -1 if no such line exists.
  115. */
  116. private static int findMediaDescriptionLine(boolean isAudio, String[] sdpLines) {
  117. final String mediaDescription = isAudio ? "m=audio " : "m=video ";
  118. for (int i = 0; i < sdpLines.length; ++i) {
  119. if (sdpLines[i].startsWith(mediaDescription)) {
  120. return i;
  121. }
  122. }
  123. return -1;
  124. }
  125. private static String movePayloadTypesToFront(List<String> preferredPayloadTypes, String mLine) {
  126. // The format of the media description line should be: m=<media> <port> <proto> <fmt> ...
  127. final List<String> origLineParts = Arrays.asList(mLine.split(" "));
  128. if (origLineParts.size() <= 3) {
  129. Log.e(TAG, "Wrong SDP media description format: " + mLine);
  130. return null;
  131. }
  132. final List<String> header = origLineParts.subList(0, 3);
  133. final List<String> unpreferredPayloadTypes =
  134. new ArrayList<String>(origLineParts.subList(3, origLineParts.size()));
  135. unpreferredPayloadTypes.removeAll(preferredPayloadTypes);
  136. // Reconstruct the line with |preferredPayloadTypes| moved to the beginning of the payload
  137. // types.
  138. final List<String> newLineParts = new ArrayList<String>();
  139. newLineParts.addAll(header);
  140. newLineParts.addAll(preferredPayloadTypes);
  141. newLineParts.addAll(unpreferredPayloadTypes);
  142. return joinString(newLineParts, " ", false /* delimiterAtEnd */);
  143. }
  144. private static String joinString(
  145. Iterable<? extends CharSequence> s, String delimiter, boolean delimiterAtEnd) {
  146. Iterator<? extends CharSequence> iter = s.iterator();
  147. if (!iter.hasNext()) {
  148. return "";
  149. }
  150. StringBuilder buffer = new StringBuilder(iter.next());
  151. while (iter.hasNext()) {
  152. buffer.append(delimiter).append(iter.next());
  153. }
  154. if (delimiterAtEnd) {
  155. buffer.append(delimiter);
  156. }
  157. return buffer.toString();
  158. }
  159. }