Remove peer connection and signaling calls from UI thread.

- Add separate looper threads for peer connection and websocket
signaling classes.
- To improve the connection speed start peer connection factory
initialization once EGL context is ready in parallel with the room
connection.
- Add asynchronious http request class and start using it in
webscoket signaling and room parameters extractor.
- Add helper looper based executor class.
- Port some of henrika changes from
https://webrtc-codereview.appspot.com/36629004/ to fix sensor
crashes on non L devices - will remove the change if CL will
be submitted soon.

R=jiayl@webrtc.org, wzh@webrtc.org

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

git-svn-id: http://webrtc.googlecode.com/svn/trunk@8006 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
glaznev@webrtc.org
2015-01-06 22:24:09 +00:00
parent 2ec50f2b0f
commit f6a9714760
15 changed files with 1234 additions and 780 deletions

View File

@@ -27,11 +27,13 @@
package org.appspot.apprtc;
import android.os.Handler;
import android.os.Looper;
import org.appspot.apprtc.AppRTCClient.SignalingParameters;
import org.appspot.apprtc.util.LooperExecutor;
import android.content.Context;
import android.opengl.EGLContext;
import android.util.Log;
import org.appspot.apprtc.AppRTCClient.SignalingParameters;
import org.webrtc.DataChannel;
import org.webrtc.IceCandidate;
import org.webrtc.MediaConstraints;
@@ -54,28 +56,32 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* PeerConnection client for AppRTC.
* Peer connection client implementation.
*
* <p>All public methods are routed to local looper thread.
* All PeerConnectionEvents callbacks are invoked from the same looper thread.
*/
public class PeerConnectionClient {
private static final String TAG = "PCRTCClient";
public static final String VIDEO_TRACK_ID = "ARDAMSv0";
public static final String AUDIO_TRACK_ID = "ARDAMSa0";
private final Handler uiHandler;
private PeerConnectionFactory factory;
private PeerConnection pc;
private final LooperExecutor executor;
private PeerConnectionFactory factory = null;
private PeerConnection pc = null;
private VideoSource videoSource;
private boolean videoSourceStopped;
private boolean videoSourceStopped = false;
private boolean isError = false;
private final PCObserver pcObserver = new PCObserver();
private final SDPObserver sdpObserver = new SDPObserver();
private final VideoRenderer.Callbacks localRender;
private final VideoRenderer.Callbacks remoteRender;
private VideoRenderer.Callbacks localRender;
private VideoRenderer.Callbacks remoteRender;
private SignalingParameters signalingParameters;
// 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 MediaConstraints sdpMediaConstraints;
private MediaConstraints videoConstraints;
private PeerConnectionEvents events;
private int startBitrate;
private boolean isInitiator;
@@ -83,184 +89,12 @@ public class PeerConnectionClient {
private SessionDescription localSdp = null; // either offer or answer SDP
private MediaStream mediaStream = null;
public PeerConnectionClient(
VideoRenderer.Callbacks localRender,
VideoRenderer.Callbacks remoteRender,
SignalingParameters signalingParameters,
PeerConnectionEvents events,
int startBitrate) {
this.localRender = localRender;
this.remoteRender = remoteRender;
this.events = events;
this.startBitrate = startBitrate;
uiHandler = new Handler(Looper.getMainLooper());
isInitiator = signalingParameters.initiator;
queuedRemoteCandidates = new LinkedList<IceCandidate>();
sdpMediaConstraints = new MediaConstraints();
sdpMediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair(
"OfferToReceiveAudio", "true"));
sdpMediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair(
"OfferToReceiveVideo", "true"));
videoConstraints = signalingParameters.videoConstraints;
factory = new PeerConnectionFactory();
MediaConstraints pcConstraints = signalingParameters.pcConstraints;
pcConstraints.optional.add(
new MediaConstraints.KeyValuePair("RtpDataChannels", "true"));
pc = factory.createPeerConnection(signalingParameters.iceServers,
pcConstraints, pcObserver);
isInitiator = false;
// Uncomment to get ALL WebRTC tracing and SENSITIVE libjingle logging.
// NOTE: this _must_ happen while |factory| is alive!
// Logging.enableTracing(
// "logcat:",
// EnumSet.of(Logging.TraceLevel.TRACE_ALL),
// Logging.Severity.LS_SENSITIVE);
mediaStream = factory.createLocalMediaStream("ARDAMS");
if (videoConstraints != null) {
mediaStream.addTrack(createVideoTrack(useFrontFacingCamera));
}
if (signalingParameters.audioConstraints != null) {
mediaStream.addTrack(factory.createAudioTrack(
AUDIO_TRACK_ID,
factory.createAudioSource(signalingParameters.audioConstraints)));
}
pc.addStream(mediaStream);
}
public boolean isHDVideo() {
if (videoConstraints == null) {
return false;
}
int minWidth = 0;
int minHeight = 0;
for (KeyValuePair keyValuePair : videoConstraints.mandatory) {
if (keyValuePair.getKey().equals("minWidth")) {
try {
minWidth = Integer.parseInt(keyValuePair.getValue());
} catch (NumberFormatException e) {
Log.e(TAG, "Can not parse video width from video constraints");
}
} else if (keyValuePair.getKey().equals("minHeight")) {
try {
minHeight = Integer.parseInt(keyValuePair.getValue());
} catch (NumberFormatException e) {
Log.e(TAG, "Can not parse video height from video constraints");
}
}
}
if (minWidth * minHeight >= 1280 * 720) {
return true;
} else {
return false;
}
}
public boolean getStats(StatsObserver observer, MediaStreamTrack track) {
return pc.getStats(observer, track);
}
public void createOffer() {
uiHandler.post(new Runnable() {
public void run() {
if (pc != null) {
isInitiator = true;
pc.createOffer(sdpObserver, sdpMediaConstraints);
}
}
});
}
public void createAnswer() {
uiHandler.post(new Runnable() {
public void run() {
if (pc != null) {
isInitiator = false;
pc.createAnswer(sdpObserver, sdpMediaConstraints);
}
}
});
}
public void addRemoteIceCandidate(final IceCandidate candidate) {
uiHandler.post(new Runnable() {
public void run() {
if (pc != null) {
if (queuedRemoteCandidates != null) {
queuedRemoteCandidates.add(candidate);
} else {
pc.addIceCandidate(candidate);
}
}
}
});
}
public void setRemoteDescription(final SessionDescription sdp) {
uiHandler.post(new Runnable() {
public void run() {
if (pc != null) {
String sdpDescription = preferISAC(sdp.description);
if (startBitrate > 0) {
sdpDescription = setStartBitrate(sdpDescription, startBitrate);
}
Log.d(TAG, "Set remote SDP.");
SessionDescription sdpRemote = new SessionDescription(
sdp.type, sdpDescription);
pc.setRemoteDescription(sdpObserver, sdpRemote);
}
}
});
}
public void stopVideoSource() {
if (videoSource != null) {
Log.d(TAG, "Stop video source.");
videoSource.stop();
videoSourceStopped = true;
}
}
public void startVideoSource() {
if (videoSource != null && videoSourceStopped) {
Log.d(TAG, "Restart video source.");
videoSource.restart();
videoSourceStopped = false;
}
}
public void close() {
uiHandler.post(new Runnable() {
public void run() {
Log.d(TAG, "Closing peer connection.");
if (pc != null) {
pc.dispose();
pc = null;
}
if (videoSource != null) {
videoSource.dispose();
videoSource = null;
}
if (factory != null) {
factory.dispose();
factory = null;
}
Log.d(TAG, "Closing peer connection done.");
events.onPeerConnectionClosed();
}
});
}
/**
* SDP/ICE ready callbacks.
*/
public static interface PeerConnectionEvents {
/**
* Callback fired once offer is created and local SDP is set.
* Callback fired once local SDP is created and set.
*/
public void onLocalDescription(final SessionDescription sdp);
@@ -289,15 +123,263 @@ public class PeerConnectionClient {
/**
* Callback fired once peer connection error happened.
*/
public void onPeerConnectionError(String description);
public void onPeerConnectionError(final String description);
}
public PeerConnectionClient() {
executor = new LooperExecutor();
}
public void createPeerConnectionFactory(
final Context context,
final boolean vp8HwAcceleration,
final EGLContext renderEGLContext,
final PeerConnectionEvents events) {
this.events = events;
executor.requestStart();
executor.execute(new Runnable() {
@Override
public void run() {
createPeerConnectionFactoryInternal(
context, vp8HwAcceleration, renderEGLContext);
}
});
}
public void createPeerConnection(
final VideoRenderer.Callbacks localRender,
final VideoRenderer.Callbacks remoteRender,
final SignalingParameters signalingParameters,
final int startBitrate) {
this.localRender = localRender;
this.remoteRender = remoteRender;
this.signalingParameters = signalingParameters;
this.startBitrate = startBitrate;
executor.execute(new Runnable() {
@Override
public void run() {
createPeerConnectionInternal();
}
});
}
public void close() {
executor.execute(new Runnable() {
@Override
public void run() {
closeInternal();
}
});
executor.requestStop();
}
private void createPeerConnectionFactoryInternal(
Context context,
boolean vp8HwAcceleration,
EGLContext renderEGLContext) {
Log.d(TAG, "Create peer connection factory.");
isError = false;
if (!PeerConnectionFactory.initializeAndroidGlobals(
context, true, true, vp8HwAcceleration, renderEGLContext)) {
events.onPeerConnectionError("Failed to initializeAndroidGlobals");
}
factory = new PeerConnectionFactory();
Log.d(TAG, "Peer connection factory created.");
}
private void createPeerConnectionInternal() {
if (factory == null || isError) {
Log.e(TAG, "Peerconnection factory is not created");
return;
}
Log.d(TAG, "Create peer connection.");
isInitiator = signalingParameters.initiator;
queuedRemoteCandidates = new LinkedList<IceCandidate>();
sdpMediaConstraints = new MediaConstraints();
sdpMediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair(
"OfferToReceiveAudio", "true"));
sdpMediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair(
"OfferToReceiveVideo", "true"));
MediaConstraints pcConstraints = signalingParameters.pcConstraints;
pcConstraints.optional.add(
new MediaConstraints.KeyValuePair("RtpDataChannels", "true"));
pc = factory.createPeerConnection(signalingParameters.iceServers,
pcConstraints, pcObserver);
isInitiator = false;
// Uncomment to get ALL WebRTC tracing and SENSITIVE libjingle logging.
// NOTE: this _must_ happen while |factory| is alive!
// Logging.enableTracing(
// "logcat:",
// EnumSet.of(Logging.TraceLevel.TRACE_ALL),
// Logging.Severity.LS_SENSITIVE);
mediaStream = factory.createLocalMediaStream("ARDAMS");
if (signalingParameters.videoConstraints != null) {
mediaStream.addTrack(createVideoTrack(useFrontFacingCamera));
}
if (signalingParameters.audioConstraints != null) {
mediaStream.addTrack(factory.createAudioTrack(
AUDIO_TRACK_ID,
factory.createAudioSource(signalingParameters.audioConstraints)));
}
pc.addStream(mediaStream);
Log.d(TAG, "Peer connection created.");
}
private void closeInternal() {
Log.d(TAG, "Closing peer connection.");
if (pc != null) {
pc.dispose();
pc = null;
}
if (videoSource != null) {
videoSource.dispose();
videoSource = null;
}
Log.d(TAG, "Closing peer connection factory.");
if (factory != null) {
factory.dispose();
factory = null;
}
Log.d(TAG, "Closing peer connection done.");
events.onPeerConnectionClosed();
}
public boolean isHDVideo() {
if (signalingParameters.videoConstraints == null) {
return false;
}
int minWidth = 0;
int minHeight = 0;
for (KeyValuePair keyValuePair :
signalingParameters.videoConstraints.mandatory) {
if (keyValuePair.getKey().equals("minWidth")) {
try {
minWidth = Integer.parseInt(keyValuePair.getValue());
} catch (NumberFormatException e) {
Log.e(TAG, "Can not parse video width from video constraints");
}
} else if (keyValuePair.getKey().equals("minHeight")) {
try {
minHeight = Integer.parseInt(keyValuePair.getValue());
} catch (NumberFormatException e) {
Log.e(TAG, "Can not parse video height from video constraints");
}
}
}
if (minWidth * minHeight >= 1280 * 720) {
return true;
} else {
return false;
}
}
public boolean getStats(StatsObserver observer, MediaStreamTrack track) {
if (pc != null && !isError) {
return pc.getStats(observer, track);
} else {
return false;
}
}
public void createOffer() {
executor.execute(new Runnable() {
@Override
public void run() {
if (pc != null && !isError) {
isInitiator = true;
pc.createOffer(sdpObserver, sdpMediaConstraints);
}
}
});
}
public void createAnswer() {
executor.execute(new Runnable() {
@Override
public void run() {
if (pc != null && !isError) {
isInitiator = false;
pc.createAnswer(sdpObserver, sdpMediaConstraints);
}
}
});
}
public void addRemoteIceCandidate(final IceCandidate candidate) {
executor.execute(new Runnable() {
@Override
public void run() {
if (pc != null && !isError) {
if (queuedRemoteCandidates != null) {
queuedRemoteCandidates.add(candidate);
} else {
pc.addIceCandidate(candidate);
}
}
}
});
}
public void setRemoteDescription(final SessionDescription sdp) {
executor.execute(new Runnable() {
@Override
public void run() {
if (pc == null || isError) {
return;
}
String sdpDescription = preferISAC(sdp.description);
if (startBitrate > 0) {
sdpDescription = setStartBitrate(sdpDescription, startBitrate);
}
Log.d(TAG, "Set remote SDP.");
SessionDescription sdpRemote = new SessionDescription(
sdp.type, sdpDescription);
pc.setRemoteDescription(sdpObserver, sdpRemote);
}
});
}
public void stopVideoSource() {
executor.execute(new Runnable() {
@Override
public void run() {
if (videoSource != null && !videoSourceStopped) {
Log.d(TAG, "Stop video source.");
videoSource.stop();
videoSourceStopped = true;
}
}
});
}
public void startVideoSource() {
executor.execute(new Runnable() {
@Override
public void run() {
if (videoSource != null && videoSourceStopped) {
Log.d(TAG, "Restart video source.");
videoSource.restart();
videoSourceStopped = false;
}
}
});
}
private void reportError(final String errorMessage) {
Log.e(TAG, "Peerconnection error: " + errorMessage);
uiHandler.post(new Runnable() {
executor.execute(new Runnable() {
@Override
public void run() {
events.onPeerConnectionError(errorMessage);
if (!isError) {
events.onPeerConnectionError(errorMessage);
isError = true;
}
}
});
}
@@ -336,7 +418,8 @@ public class PeerConnectionClient {
videoSource.dispose();
}
videoSource = factory.createVideoSource(capturer, videoConstraints);
videoSource = factory.createVideoSource(
capturer, signalingParameters.videoConstraints);
String trackExtension = frontFacing ? "frontFacing" : "backFacing";
VideoTrack videoTrack =
factory.createVideoTrack(VIDEO_TRACK_ID + trackExtension, videoSource);
@@ -344,13 +427,6 @@ public class PeerConnectionClient {
return videoTrack;
}
// Poor-man's assert(): die with |msg| unless |condition| is true.
private void abortUnless(boolean condition, String msg) {
if (!condition) {
reportError(msg);
}
}
private static String setStartBitrate(
String sdpDescription, int bitrateKbps) {
String[] lines = sdpDescription.split("\r\n");
@@ -443,16 +519,16 @@ public class PeerConnectionClient {
}
}
public void switchCamera() {
if (videoConstraints == null) {
private void switchCameraInternal() {
if (signalingParameters.videoConstraints == null) {
return; // No video is sent.
}
if (pc.signalingState() != PeerConnection.SignalingState.STABLE) {
Log.e(TAG, "Switching camera during negotiation is not handled.");
return;
}
Log.d(TAG, "Switch camera");
pc.removeStream(mediaStream);
VideoTrack currentTrack = mediaStream.videoTracks.get(0);
mediaStream.removeTrack(currentTrack);
@@ -485,13 +561,26 @@ public class PeerConnectionClient {
pc.setRemoteDescription(new SwitchCameraSdbObserver(), remoteDesc);
pc.setLocalDescription(new SwitchCameraSdbObserver(), localSdp);
}
Log.d(TAG, "Switch camera done");
}
public void switchCamera() {
executor.execute(new Runnable() {
@Override
public void run() {
if (pc != null && !isError) {
switchCameraInternal();
}
}
});
}
// Implementation detail: observe ICE & stream changes and react accordingly.
private class PCObserver implements PeerConnection.Observer {
@Override
public void onIceCandidate(final IceCandidate candidate){
uiHandler.post(new Runnable() {
executor.execute(new Runnable() {
@Override
public void run() {
events.onIceCandidate(candidate);
}
@@ -506,23 +595,20 @@ public class PeerConnectionClient {
@Override
public void onIceConnectionChange(
PeerConnection.IceConnectionState newState) {
Log.d(TAG, "IceConnectionState: " + newState);
if (newState == IceConnectionState.CONNECTED) {
uiHandler.post(new Runnable() {
public void run() {
final PeerConnection.IceConnectionState newState) {
executor.execute(new Runnable() {
@Override
public void run() {
Log.d(TAG, "IceConnectionState: " + newState);
if (newState == IceConnectionState.CONNECTED) {
events.onIceConnected();
}
});
} else if (newState == IceConnectionState.DISCONNECTED) {
uiHandler.post(new Runnable() {
public void run() {
} else if (newState == IceConnectionState.DISCONNECTED) {
events.onIceDisconnected();
} else if (newState == IceConnectionState.FAILED) {
reportError("ICE connection failed.");
}
});
} else if (newState == IceConnectionState.FAILED) {
reportError("ICE connection failed.");
}
}
});
}
@Override
@@ -533,11 +619,16 @@ public class PeerConnectionClient {
@Override
public void onAddStream(final MediaStream stream){
uiHandler.post(new Runnable() {
executor.execute(new Runnable() {
@Override
public void run() {
abortUnless(stream.audioTracks.size() <= 1
&& stream.videoTracks.size() <= 1,
"Weird-looking stream: " + stream);
if (pc == null || isError) {
return;
}
if (stream.audioTracks.size() > 1 || stream.videoTracks.size() > 1) {
reportError("Weird-looking stream: " + stream);
return;
}
if (stream.videoTracks.size() == 1) {
stream.videoTracks.get(0).addRenderer(
new VideoRenderer(remoteRender));
@@ -548,8 +639,12 @@ public class PeerConnectionClient {
@Override
public void onRemoveStream(final MediaStream stream){
uiHandler.post(new Runnable() {
executor.execute(new Runnable() {
@Override
public void run() {
if (pc == null || isError) {
return;
}
stream.videoTracks.get(0).dispose();
}
});
@@ -573,13 +668,17 @@ public class PeerConnectionClient {
private class SDPObserver implements SdpObserver {
@Override
public void onCreateSuccess(final SessionDescription origSdp) {
abortUnless(localSdp == null, "multiple SDP create?!?");
if (localSdp != null) {
reportError("Multiple SDP create.");
return;
}
final SessionDescription sdp = new SessionDescription(
origSdp.type, preferISAC(origSdp.description));
localSdp = sdp;
uiHandler.post(new Runnable() {
executor.execute(new Runnable() {
@Override
public void run() {
if (pc != null) {
if (pc != null && !isError) {
Log.d(TAG, "Set local SDP from " + sdp.type);
pc.setLocalDescription(sdpObserver, sdp);
}
@@ -589,9 +688,10 @@ public class PeerConnectionClient {
@Override
public void onSetSuccess() {
uiHandler.post(new Runnable() {
executor.execute(new Runnable() {
@Override
public void run() {
if (pc == null) {
if (pc == null || isError) {
return;
}
if (isInitiator) {