Port some fixes in AppRTCDemo.

- Make PeerConnectionClient a singleton.
- Fix crash in CpuMonitor.
- Remove reading constraints from room response.
- Catch and report camera errors.

R=wzh@webrtc.org

Review URL: https://webrtc-codereview.appspot.com/43059004

Cr-Commit-Position: refs/heads/master@{#8930}
This commit is contained in:
Alex Glaznev 2015-04-06 14:02:19 -07:00
parent be508a1d36
commit e095148869
8 changed files with 125 additions and 164 deletions

View File

@ -28,7 +28,6 @@
package org.appspot.apprtc;
import org.webrtc.IceCandidate;
import org.webrtc.MediaConstraints;
import org.webrtc.PeerConnection;
import org.webrtc.SessionDescription;
@ -87,9 +86,6 @@ public interface AppRTCClient {
public static class SignalingParameters {
public final List<PeerConnection.IceServer> iceServers;
public final boolean initiator;
public final MediaConstraints pcConstraints;
public final MediaConstraints videoConstraints;
public final MediaConstraints audioConstraints;
public final String clientId;
public final String wssUrl;
public final String wssPostUrl;
@ -98,15 +94,11 @@ public interface AppRTCClient {
public SignalingParameters(
List<PeerConnection.IceServer> iceServers,
boolean initiator, MediaConstraints pcConstraints,
MediaConstraints videoConstraints, MediaConstraints audioConstraints,
String clientId, String wssUrl, String wssPostUrl,
boolean initiator, String clientId,
String wssUrl, String wssPostUrl,
SessionDescription offerSdp, List<IceCandidate> iceCandidates) {
this.iceServers = iceServers;
this.initiator = initiator;
this.pcConstraints = pcConstraints;
this.videoConstraints = videoConstraints;
this.audioConstraints = audioConstraints;
this.clientId = clientId;
this.wssUrl = wssUrl;
this.wssPostUrl = wssPostUrl;

View File

@ -383,7 +383,7 @@ public class CallActivity extends Activity
if (peerConnectionClient == null) {
final long delta = System.currentTimeMillis() - callStartedTimeMs;
Log.d(TAG, "Creating peer connection factory, delay=" + delta + "ms");
peerConnectionClient = new PeerConnectionClient();
peerConnectionClient = PeerConnectionClient.getInstance();
peerConnectionClient.createPeerConnectionFactory(CallActivity.this,
VideoRendererGui.getEGLContext(), peerConnectionParameters,
CallActivity.this);

View File

@ -113,7 +113,8 @@ class CpuMonitor {
Scanner scanner = new Scanner(rdr).useDelimiter("[-\n]");
scanner.nextInt(); // Skip leading number 0.
cpusPresent = 1 + scanner.nextInt();
} catch (InputMismatchException e) {
scanner.close();
} catch (Exception e) {
Log.e(TAG, "Cannot do CPU stats due to /sys/devices/system/cpu/present parsing problem");
} finally {
fin.close();
@ -264,7 +265,8 @@ class CpuMonitor {
BufferedReader rdr = new BufferedReader(fin);
Scanner scannerC = new Scanner(rdr);
number = scannerC.nextLong();
} catch (InputMismatchException e) {
scannerC.close();
} catch (Exception e) {
// CPU presumably got offline just after we opened file.
} finally {
fin.close();
@ -295,7 +297,8 @@ class CpuMonitor {
long sys = scanner.nextLong();
runTime = user + nice + sys;
idleTime = scanner.nextLong();
} catch (InputMismatchException e) {
scanner.close();
} catch (Exception e) {
Log.e(TAG, "Problems parsing /proc/stat");
return null;
} finally {

View File

@ -35,6 +35,7 @@ import org.appspot.apprtc.AppRTCClient.SignalingParameters;
import org.appspot.apprtc.util.LooperExecutor;
import org.webrtc.DataChannel;
import org.webrtc.IceCandidate;
import org.webrtc.Logging;
import org.webrtc.MediaCodecVideoEncoder;
import org.webrtc.MediaConstraints;
import org.webrtc.MediaConstraints.KeyValuePair;
@ -51,6 +52,7 @@ import org.webrtc.VideoRenderer;
import org.webrtc.VideoSource;
import org.webrtc.VideoTrack;
import java.util.EnumSet;
import java.util.LinkedList;
import java.util.Timer;
import java.util.TimerTask;
@ -62,6 +64,7 @@ import java.util.regex.Pattern;
*
* <p>All public methods are routed to local looper thread.
* All PeerConnectionEvents callbacks are invoked from the same looper thread.
* This class is a singleton.
*/
public class PeerConnectionClient {
public static final String VIDEO_TRACK_ID = "ARDAMSv0";
@ -89,18 +92,21 @@ public class PeerConnectionClient {
private static final int MAX_VIDEO_HEIGHT = 1280;
private static final int MAX_VIDEO_FPS = 30;
private final LooperExecutor executor;
private PeerConnectionFactory factory = null;
private PeerConnection peerConnection = null;
private VideoSource videoSource;
private boolean videoCallEnabled = true;
private boolean preferIsac = false;
private boolean preferH264 = false;
private boolean videoSourceStopped = false;
private boolean isError = false;
private final Timer statsTimer = new Timer();
private static final PeerConnectionClient instance = new PeerConnectionClient();
private final PCObserver pcObserver = new PCObserver();
private final SDPObserver sdpObserver = new SDPObserver();
private final LooperExecutor executor;
private PeerConnectionFactory factory;
private PeerConnection peerConnection;
PeerConnectionFactory.Options options = null;
private VideoSource videoSource;
private boolean videoCallEnabled;
private boolean preferIsac;
private boolean preferH264;
private boolean videoSourceStopped;
private boolean isError;
private Timer statsTimer;
private VideoRenderer.Callbacks localRender;
private VideoRenderer.Callbacks remoteRender;
private SignalingParameters signalingParameters;
@ -112,17 +118,17 @@ public class PeerConnectionClient {
// Queued remote ICE candidates are consumed only after both local and
// remote descriptions are set. Similarly local ICE candidates are sent to
// remote peer after both local and remote description are set.
private LinkedList<IceCandidate> queuedRemoteCandidates = null;
private LinkedList<IceCandidate> queuedRemoteCandidates;
private PeerConnectionEvents events;
private boolean isInitiator;
private SessionDescription localSdp = null; // either offer or answer SDP
private MediaStream mediaStream = null;
private SessionDescription localSdp; // either offer or answer SDP
private MediaStream mediaStream;
private int numberOfCameras;
private VideoCapturerAndroid videoCapturer = null;
private VideoCapturerAndroid videoCapturer;
// enableVideo is set to true if video should be rendered and sent.
private boolean renderVideo = true;
private VideoTrack localVideoTrack = null;
private VideoTrack remoteVideoTrack = null;
private boolean renderVideo;
private VideoTrack localVideoTrack;
private VideoTrack remoteVideoTrack;
/**
* Peer connection parameters.
@ -202,8 +208,20 @@ public class PeerConnectionClient {
public void onPeerConnectionError(final String description);
}
public PeerConnectionClient() {
private PeerConnectionClient() {
executor = new LooperExecutor();
// Looper thread is started once in private ctor and is used for all
// peer connection API calls to ensure new peer connection factory is
// created on the same thread as previously destroyed factory.
executor.requestStart();
}
public static PeerConnectionClient getInstance() {
return instance;
}
public void setPeerConnectionFactoryOptions(PeerConnectionFactory.Options options) {
this.options = options;
}
public void createPeerConnectionFactory(
@ -214,7 +232,22 @@ public class PeerConnectionClient {
this.peerConnectionParameters = peerConnectionParameters;
this.events = events;
videoCallEnabled = peerConnectionParameters.videoCallEnabled;
executor.requestStart();
// Reset variables to initial states.
factory = null;
peerConnection = null;
preferIsac = false;
preferH264 = false;
videoSourceStopped = false;
isError = false;
queuedRemoteCandidates = null;
localSdp = null; // either offer or answer SDP
mediaStream = null;
videoCapturer = null;
renderVideo = true;
localVideoTrack = null;
remoteVideoTrack = null;
statsTimer = new Timer();
executor.execute(new Runnable() {
@Override
public void run() {
@ -250,7 +283,10 @@ public class PeerConnectionClient {
closeInternal();
}
});
executor.requestStop();
}
public boolean isVideoCallEnabled() {
return videoCallEnabled;
}
private void createPeerConnectionFactoryInternal(
@ -284,14 +320,11 @@ public class PeerConnectionClient {
events.onPeerConnectionError("Failed to initializeAndroidGlobals");
}
factory = new PeerConnectionFactory();
configureFactory(factory);
Log.d(TAG, "Peer connection factory created.");
if (options != null) {
Log.d(TAG, "Factory networkIgnoreMask option: " + options.networkIgnoreMask);
factory.setOptions(options);
}
/**
* Hook where tests can provide additional configuration for the factory.
*/
protected void configureFactory(PeerConnectionFactory factory) {
Log.d(TAG, "Peer connection factory created.");
}
private void createMediaConstraintsInternal() {
@ -384,12 +417,12 @@ public class PeerConnectionClient {
signalingParameters.iceServers, pcConstraints, pcObserver);
isInitiator = false;
// Uncomment to get ALL WebRTC tracing and SENSITIVE libjingle logging.
// Set default WebRTC tracing and INFO libjingle logging.
// NOTE: this _must_ happen while |factory| is alive!
// Logging.enableTracing(
// "logcat:",
// EnumSet.of(Logging.TraceLevel.TRACE_ALL),
// Logging.Severity.LS_SENSITIVE);
Logging.enableTracing(
"logcat:",
EnumSet.of(Logging.TraceLevel.TRACE_DEFAULT),
Logging.Severity.LS_INFO);
mediaStream = factory.createLocalMediaStream("ARDAMS");
if (videoCallEnabled) {
@ -401,6 +434,10 @@ public class PeerConnectionClient {
}
Log.d(TAG, "Opening camera: " + cameraDeviceName);
videoCapturer = VideoCapturerAndroid.create(cameraDeviceName);
if (videoCapturer == null) {
reportError("Failed to open camera");
return;
}
mediaStream.addTrack(createVideoTrack(videoCapturer));
}
@ -419,6 +456,7 @@ public class PeerConnectionClient {
peerConnection.dispose();
peerConnection = null;
}
Log.d(TAG, "Closing video source.");
if (videoSource != null) {
videoSource.dispose();
videoSource = null;
@ -428,6 +466,7 @@ public class PeerConnectionClient {
factory.dispose();
factory = null;
}
options = null;
Log.d(TAG, "Closing peer connection done.");
events.onPeerConnectionClosed();
}
@ -477,6 +516,7 @@ public class PeerConnectionClient {
public void enableStatsEvents(boolean enable, int periodMs) {
if (enable) {
try {
statsTimer.schedule(new TimerTask() {
@Override
public void run() {
@ -488,6 +528,9 @@ public class PeerConnectionClient {
});
}
}, 0, periodMs);
} catch (Exception e) {
Log.e(TAG, "Can not schedule statistics timer", e);
}
} else {
statsTimer.cancel();
}
@ -769,8 +812,10 @@ public class PeerConnectionClient {
}
private void switchCameraInternal() {
if (!videoCallEnabled || numberOfCameras < 2) {
return; // No video is sent or only one camera is available.
if (!videoCallEnabled || numberOfCameras < 2 || isError || videoCapturer == null) {
Log.e(TAG, "Failed to switch camera. Video: " + videoCallEnabled + ". Error : "
+ isError + ". Number of cameras: " + numberOfCameras);
return; // No video is sent or only one camera is available or error happened.
}
Log.d(TAG, "Switch camera");
videoCapturer.switchCamera();
@ -780,10 +825,8 @@ public class PeerConnectionClient {
executor.execute(new Runnable() {
@Override
public void run() {
if (peerConnection != null && !isError) {
switchCameraInternal();
}
}
});
}

View File

@ -37,7 +37,6 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.webrtc.IceCandidate;
import org.webrtc.MediaConstraints;
import org.webrtc.PeerConnection;
import org.webrtc.SessionDescription;
@ -170,18 +169,8 @@ public class RoomParametersFetcher {
}
}
MediaConstraints pcConstraints = constraintsFromJSON(roomJson.getString("pc_constraints"));
Log.d(TAG, "pcConstraints: " + pcConstraints);
MediaConstraints videoConstraints = constraintsFromJSON(
getAVConstraints("video", roomJson.getString("media_constraints")));
Log.d(TAG, "videoConstraints: " + videoConstraints);
MediaConstraints audioConstraints = constraintsFromJSON(
getAVConstraints("audio", roomJson.getString("media_constraints")));
Log.d(TAG, "audioConstraints: " + audioConstraints);
SignalingParameters params = new SignalingParameters(
iceServers, initiator,
pcConstraints, videoConstraints, audioConstraints,
clientId, wssUrl, wssPostUrl,
offerSdp, iceCandidates);
events.onSignalingParametersReady(params);
@ -193,59 +182,6 @@ public class RoomParametersFetcher {
}
}
// Return the constraints specified for |type| of "audio" or "video" in
// |mediaConstraintsString|.
private String getAVConstraints (
String type, String mediaConstraintsString) throws JSONException {
JSONObject json = new JSONObject(mediaConstraintsString);
// Tricky handling of values that are allowed to be (boolean or
// MediaTrackConstraints) by the getUserMedia() spec. There are three
// cases below.
if (!json.has(type) || !json.optBoolean(type, true)) {
// Case 1: "audio"/"video" is not present, or is an explicit "false"
// boolean.
return null;
}
if (json.optBoolean(type, false)) {
// Case 2: "audio"/"video" is an explicit "true" boolean.
return "{\"mandatory\": {}, \"optional\": []}";
}
// Case 3: "audio"/"video" is an object.
return json.getJSONObject(type).toString();
}
private MediaConstraints constraintsFromJSON(String jsonString)
throws JSONException {
if (jsonString == null) {
return null;
}
MediaConstraints constraints = new MediaConstraints();
JSONObject json = new JSONObject(jsonString);
JSONObject mandatoryJSON = json.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 = json.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));
}
}
return constraints;
}
// Requests & returns a TURN ICE Server based on a request URL. Must be run
// off the main thread!
private LinkedList<PeerConnection.IceServer> requestTurnServers(String url)

View File

@ -127,7 +127,7 @@ public class WebSocketChannelClient {
this.roomID = roomID;
this.clientID = clientID;
if (state != WebSocketConnectionState.CONNECTED) {
Log.d(TAG, "WebSocket register() in state " + state);
Log.w(TAG, "WebSocket register() in state " + state);
return;
}
Log.d(TAG, "Registering WebSocket for room " + roomID + ". CLientID: " + clientID);
@ -190,17 +190,16 @@ public class WebSocketChannelClient {
checkIfCalledOnValidThread();
Log.d(TAG, "Disonnect WebSocket. State: " + state);
if (state == WebSocketConnectionState.REGISTERED) {
// Send "bye" to WebSocket server.
send("{\"type\": \"bye\"}");
state = WebSocketConnectionState.CONNECTED;
// Send http DELETE to http WebSocket server.
sendWSSMessage("DELETE", "");
}
// Close WebSocket in CONNECTED or ERROR states only.
if (state == WebSocketConnectionState.CONNECTED
|| state == WebSocketConnectionState.ERROR) {
ws.disconnect();
// Send DELETE to http WebSocket server.
sendWSSMessage("DELETE", "");
state = WebSocketConnectionState.CLOSED;
// Wait for websocket close event to prevent websocket library from

View File

@ -45,6 +45,7 @@ public class AsyncHttpURLConnection {
private final String url;
private final String message;
private final AsyncHttpEvents events;
private String contentType;
/**
* Http requests callbacks.
@ -62,6 +63,10 @@ public class AsyncHttpURLConnection {
this.events = events;
}
public void setContentType(String contentType) {
this.contentType = contentType;
}
public void send() {
Runnable runHttp = new Runnable() {
public void run() {
@ -92,8 +97,11 @@ public class AsyncHttpURLConnection {
connection.setDoOutput(true);
connection.setFixedLengthStreamingMode(postData.length);
}
connection.setRequestProperty(
"content-type", "text/plain; charset=utf-8");
if (contentType == null) {
connection.setRequestProperty("Content-Type", "text/plain; charset=utf-8");
} else {
connection.setRequestProperty("Content-Type", contentType);
}
// Send POST request.
if (doOutput && postData.length > 0) {
@ -105,9 +113,9 @@ public class AsyncHttpURLConnection {
// Get response.
int responseCode = connection.getResponseCode();
if (responseCode != 200) {
connection.disconnect();
events.onHttpError("Non-200 response to " + method + " to URL: "
+ url + " : " + connection.getHeaderField(null));
connection.disconnect();
return;
}
InputStream responseStream = connection.getInputStream();

View File

@ -61,7 +61,6 @@ public class PeerConnectionClientTest extends InstrumentationTestCase
private static final String VIDEO_CODEC_VP9 = "VP9";
private static final String VIDEO_CODEC_H264 = "H264";
private static final int AUDIO_RUN_TIMEOUT = 1000;
private static final String DTLS_SRTP_KEY_AGREEMENT_CONSTRAINT = "DtlsSrtpKeyAgreement";
private static final String LOCAL_RENDERER_NAME = "Local renderer";
private static final String REMOTE_RENDERER_NAME = "Remote renderer";
@ -130,17 +129,6 @@ public class PeerConnectionClientTest extends InstrumentationTestCase
}
}
// Test instance of the PeerConnectionClient class that overrides the options
// for the factory so we can run the test without an Internet connection.
class TestPeerConnectionClient extends PeerConnectionClient {
protected void configureFactory(PeerConnectionFactory factory) {
PeerConnectionFactory.Options options =
new PeerConnectionFactory.Options();
options.networkIgnoreMask = 0;
factory.setOptions(options);
}
}
// Peer connection events implementation.
@Override
public void onLocalDescription(SessionDescription sdp) {
@ -251,33 +239,25 @@ public class PeerConnectionClientTest extends InstrumentationTestCase
}
}
private SignalingParameters getTestSignalingParameters() {
List<PeerConnection.IceServer> iceServers =
new LinkedList<PeerConnection.IceServer>();
MediaConstraints pcConstraints = new MediaConstraints();
pcConstraints.optional.add(
new MediaConstraints.KeyValuePair(DTLS_SRTP_KEY_AGREEMENT_CONSTRAINT, "false"));
MediaConstraints videoConstraints = new MediaConstraints();
MediaConstraints audioConstraints = new MediaConstraints();
SignalingParameters signalingParameters = new SignalingParameters(
iceServers, true,
pcConstraints, videoConstraints, audioConstraints,
null, null, null,
null, null);
return signalingParameters;
}
PeerConnectionClient createPeerConnectionClient(
MockRenderer localRenderer, MockRenderer remoteRenderer,
boolean enableVideo, String videoCodec) {
SignalingParameters signalingParameters = getTestSignalingParameters();
List<PeerConnection.IceServer> iceServers =
new LinkedList<PeerConnection.IceServer>();
SignalingParameters signalingParameters = new SignalingParameters(
iceServers, true, // iceServers, initiator.
null, null, null, // clientId, wssUrl, wssPostUrl.
null, null); // offerSdp, iceCandidates.
PeerConnectionParameters peerConnectionParameters =
new PeerConnectionParameters(
enableVideo, true, // videoCallEnabled, loopback.
0, 0, 0, 0, videoCodec, true, // video codec parameters.
0, "OPUS", true); // audio codec parameters.
PeerConnectionClient client = new TestPeerConnectionClient();
PeerConnectionClient client = PeerConnectionClient.getInstance();
PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
options.networkIgnoreMask = 0;
client.setPeerConnectionFactoryOptions(options);
client.createPeerConnectionFactory(
getInstrumentation().getContext(), null,
peerConnectionParameters, this);