/*
* Copyright (c) 2014 The MITRE Corporation, All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this work 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.
*/
// Derived from GAEChannelClient from the libjingle / webrtc AppRTCDemo
// example application distributed under the following license.
/*
* libjingle
* Copyright 2013, Google Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.mitre.svmp.apprtc;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.mitre.svmp.protocol.SVMPProtocol.Request;
import org.mitre.svmp.protocol.SVMPProtocol.VideoStreamInfo;
import org.mitre.svmp.protocol.SVMPProtocol.WebRTCMessage;
import org.webrtc.MediaConstraints;
import org.webrtc.PeerConnection.IceServer;
import java.util.LinkedList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author Joe Portner
* Contains a number of helper functions used throughout AppRTC code
*/
public class AppRTCHelper {
private static final String TAG = AppRTCHelper.class.getName();
public static Request makeWebRTCRequest(JSONObject json) {
WebRTCMessage.Builder rtcmsg = WebRTCMessage.newBuilder();
rtcmsg.setJson(json.toString());
try {
Log.d(TAG, "Sending WebRTC message: " + json.toString(4));
} catch (JSONException e) {
// whatever
}
return Request.newBuilder()
.setType(Request.RequestType.WEBRTC)
.setWebrtcMsg(rtcmsg)
.build();
}
// Poor-man's assert(): die with |msg| unless |condition| is true.
public static void abortUnless(boolean condition, String msg) {
if (!condition) {
throw new RuntimeException(msg);
}
}
// Put a |key|->|value| mapping in |json|.
public static void jsonPut(JSONObject json, String key, Object value) {
try {
json.put(key, value);
} catch (JSONException e) {
Log.e(TAG, "Failed to put key/value pair in JSON: " + e.getMessage());
}
}
public static AppRTCSignalingParameters getParametersForRoom(JSONObject jsonObject) {
AppRTCSignalingParameters value = null;
try {
MediaConstraints pcConstraints = constraintsFromJSON(jsonObject.getJSONObject("pc"));
Log.d(TAG, "pc constraints: " + pcConstraints);
MediaConstraints videoConstraints = constraintsFromJSON(jsonObject.getJSONObject("video"));
Log.d(TAG, "video constraints: " + videoConstraints);
LinkedList<IceServer> iceServers = iceServersFromPCConfigJSON(jsonObject.getJSONArray("ice_servers"));
value = new AppRTCSignalingParameters(iceServers, true, pcConstraints, videoConstraints);
} catch (JSONException e) {
Log.e(TAG, "getParametersForRoom failed:", e);
}
return value;
}
// Mangle SDP to prefer ISAC/16000 over any other audio codec.
public static String preferISAC(String sdpDescription) {
String[] lines = sdpDescription.split("\r\n");
int mLineIndex = -1;
String isac16kRtpMap = null;
Pattern isac16kPattern =
Pattern.compile("^a=rtpmap:(\\d+) ISAC/16000[\r]?$");
for (int i = 0;
(i < lines.length) && (mLineIndex == -1 || isac16kRtpMap == null);
++i) {
if (lines[i].startsWith("m=audio ")) {
mLineIndex = i;
continue;
}
Matcher isac16kMatcher = isac16kPattern.matcher(lines[i]);
if (isac16kMatcher.matches()) {
isac16kRtpMap = isac16kMatcher.group(1);
continue;
}
}
if (mLineIndex == -1) {
Log.d(TAG, "No m=audio line, so can't prefer iSAC");
return sdpDescription;
}
if (isac16kRtpMap == null) {
Log.d(TAG, "No ISAC/16000 line, so can't prefer iSAC");
return sdpDescription;
}
String[] origMLineParts = lines[mLineIndex].split(" ");
StringBuilder newMLine = new StringBuilder();
int origPartIndex = 0;
// Format is: m=<media> <port> <proto> <fmt> ...
newMLine.append(origMLineParts[origPartIndex++]).append(" ");
newMLine.append(origMLineParts[origPartIndex++]).append(" ");
newMLine.append(origMLineParts[origPartIndex++]).append(" ");
newMLine.append(isac16kRtpMap);
for (; origPartIndex < origMLineParts.length; ++origPartIndex) {
if (!origMLineParts[origPartIndex].equals(isac16kRtpMap)) {
newMLine.append(" ").append(origMLineParts[origPartIndex]);
}
}
lines[mLineIndex] = newMLine.toString();
StringBuilder newSdpDescription = new StringBuilder();
for (String line : lines) {
newSdpDescription.append(line).append("\r\n");
}
return newSdpDescription.toString();
}
private static MediaConstraints constraintsFromJSON(JSONObject jsonObject) {
MediaConstraints constraints = new MediaConstraints();
try {
JSONObject mandatoryJSON = jsonObject.optJSONObject("mandatory");
if (mandatoryJSON != null) {
JSONArray mandatoryKeys = mandatoryJSON.names();
if (mandatoryKeys != null) {
for (int i = 0; i < mandatoryKeys.length(); ++i) {
String key = mandatoryKeys.getString(i);
String value = mandatoryJSON.getString(key);
constraints.mandatory.add(
new MediaConstraints.KeyValuePair(key, value));
}
}
}
JSONArray optionalJSON = jsonObject.optJSONArray("optional");
if (optionalJSON != null) {
for (int i = 0; i < optionalJSON.length(); ++i) {
JSONObject keyValueDict = optionalJSON.getJSONObject(i);
String key = keyValueDict.names().getString(0);
String value = keyValueDict.getString(key);
constraints.optional.add(
new MediaConstraints.KeyValuePair(key, value));
}
}
} catch (JSONException e) {
Log.e(TAG, "Failed to parse MediaConstraints from JSON: " + e.getMessage());
}
return constraints;
}
// Return the list of ICE servers described by a WebRTCPeerConnection
// configuration string.
private static LinkedList<IceServer> iceServersFromPCConfigJSON(JSONArray jsonArray) {
LinkedList<IceServer> ret = new LinkedList<IceServer>();
try {
Log.d(TAG, "ICE server JSON: " + jsonArray.toString(4));
for (int i = 0; i < jsonArray.length(); ++i) {
JSONObject server = jsonArray.getJSONObject(i);
String url = server.getString("url");
String username =
server.has("username") ? server.getString("username") : "";
String credential =
server.has("credential") ? server.getString("credential") : "";
credential =
server.has("password") ? server.getString("password") : credential;
ret.add(new IceServer(url, username, credential));
}
} catch (JSONException e) {
Log.e(TAG, "Failed to parse ICE Servers from PC Config JSON: " + e.getMessage());
}
return ret;
}
}