Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(677)

Unified Diff: webrtc/api/androidtests/src/org/webrtc/PeerConnectionTest.java

Issue 1639583003: Fixing bug in MediaStream.java that caused double disposal of track. (Closed) Base URL: https://chromium.googlesource.com/external/webrtc.git@master
Patch Set: Fixing placement of GUARDED_BY. Created 4 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | webrtc/api/java/src/org/webrtc/MediaStream.java » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: webrtc/api/androidtests/src/org/webrtc/PeerConnectionTest.java
diff --git a/webrtc/api/androidtests/src/org/webrtc/PeerConnectionTest.java b/webrtc/api/androidtests/src/org/webrtc/PeerConnectionTest.java
index 98aa82fcb5d88dde3e3dfd48090d7727697d0874..bbaa15287e82330c264791bd1286896ee72ab52b 100644
--- a/webrtc/api/androidtests/src/org/webrtc/PeerConnectionTest.java
+++ b/webrtc/api/androidtests/src/org/webrtc/PeerConnectionTest.java
@@ -24,6 +24,7 @@ import java.util.Arrays;
import java.util.IdentityHashMap;
import java.util.LinkedList;
import java.util.List;
+import java.util.HashSet;
import java.util.Map;
import java.util.TreeSet;
import java.util.concurrent.CountDownLatch;
@@ -50,8 +51,8 @@ public class PeerConnectionTest extends ActivityTestCase {
private int expectedIceCandidates = 0;
private int expectedErrors = 0;
private int expectedRenegotiations = 0;
- private int previouslySeenWidth = 0;
- private int previouslySeenHeight = 0;
+ private int expectedWidth = 0;
+ private int expectedHeight = 0;
private int expectedFramesDelivered = 0;
private LinkedList<SignalingState> expectedSignalingChanges =
new LinkedList<SignalingState>();
@@ -77,6 +78,8 @@ public class PeerConnectionTest extends ActivityTestCase {
private int expectedStatsCallbacks = 0;
private LinkedList<StatsReport[]> gotStatsReports =
new LinkedList<StatsReport[]>();
+ private final HashSet<MediaStream> gotRemoteStreams =
+ new HashSet<MediaStream>();
public ObserverExpectations(String name) {
this.name = name;
@@ -106,18 +109,9 @@ public class PeerConnectionTest extends ActivityTestCase {
}
}
- private synchronized void setSize(int width, int height) {
- // Because different camera devices (fake & physical) produce different
- // resolutions, we only sanity-check the set sizes,
- assertTrue(width > 0);
- assertTrue(height > 0);
- if (previouslySeenWidth > 0) {
- assertEquals(previouslySeenWidth, width);
- assertEquals(previouslySeenHeight, height);
- } else {
- previouslySeenWidth = width;
- previouslySeenHeight = height;
- }
+ public synchronized void setExpectedResolution(int width, int height) {
+ expectedWidth = width;
+ expectedHeight = height;
}
public synchronized void expectFramesDelivered(int count) {
@@ -126,7 +120,10 @@ public class PeerConnectionTest extends ActivityTestCase {
@Override
public synchronized void renderFrame(VideoRenderer.I420Frame frame) {
- setSize(frame.rotatedWidth(), frame.rotatedHeight());
+ assertTrue(expectedWidth > 0);
+ assertTrue(expectedHeight > 0);
+ assertEquals(expectedWidth, frame.rotatedWidth());
+ assertEquals(expectedHeight, frame.rotatedHeight());
--expectedFramesDelivered;
VideoRenderer.renderFrameDone(frame);
}
@@ -200,6 +197,7 @@ public class PeerConnectionTest extends ActivityTestCase {
stream.videoTracks.get(0).addRenderer(renderer);
assertNull(renderers.put(
stream, new WeakReference<VideoRenderer>(renderer)));
+ gotRemoteStreams.add(stream);
}
public synchronized void expectRemoveStream(String label) {
@@ -214,6 +212,7 @@ public class PeerConnectionTest extends ActivityTestCase {
assertNotNull(renderer.get());
assertEquals(1, stream.videoTracks.size());
stream.videoTracks.get(0).removeRenderer(renderer.get());
+ gotRemoteStreams.remove(stream);
}
public synchronized void expectDataChannel(String label) {
@@ -388,6 +387,25 @@ public class PeerConnectionTest extends ActivityTestCase {
}
}
+ // Sets the expected resolution for an ObserverExpectations once a frame
+ // has been captured.
+ private static class ExpectedResolutionSetter implements VideoRenderer.Callbacks {
+ private ObserverExpectations observer;
+
+ public ExpectedResolutionSetter(ObserverExpectations observer) {
+ this.observer = observer;
+ }
+
+ @Override
+ public synchronized void renderFrame(VideoRenderer.I420Frame frame) {
+ // Because different camera devices (fake & physical) produce different
+ // resolutions, we only sanity-check the set sizes,
+ assertTrue(frame.rotatedWidth() > 0);
+ assertTrue(frame.rotatedHeight() > 0);
+ observer.setExpectedResolution(frame.rotatedWidth(), frame.rotatedHeight());
+ }
+ }
+
private static class SdpObserverLatch implements SdpObserver {
private boolean success = false;
private SessionDescription sdp = null;
@@ -496,6 +514,17 @@ public class PeerConnectionTest extends ActivityTestCase {
// Thread.sleep(100);
}
+ // TODO(fischman) MOAR test ideas:
+ // - Test that PC.removeStream() works; requires a second
+ // createOffer/createAnswer dance.
+ // - audit each place that uses |constraints| for specifying non-trivial
+ // constraints (and ensure they're honored).
+ // - test error cases
+ // - ensure reasonable coverage of _jni.cc is achieved. Coverage is
+ // extra-important because of all the free-text (class/method names, etc)
+ // in JNI-style programming; make sure no typos!
+ // - Test that shutdown mid-interaction is crash-free.
+
@MediumTest
public void testCompleteSession() throws Exception {
// Allow loopback interfaces too since our Android devices often don't
@@ -540,7 +569,8 @@ public class PeerConnectionTest extends ActivityTestCase {
offeringExpectations.expectRenegotiationNeeded();
WeakReference<MediaStream> oLMS = addTracksToPC(
factory, offeringPC, videoSource, "offeredMediaStream",
- "offeredVideoTrack", "offeredAudioTrack", offeringExpectations);
+ "offeredVideoTrack", "offeredAudioTrack",
+ new ExpectedResolutionSetter(answeringExpectations));
offeringExpectations.expectRenegotiationNeeded();
DataChannel offeringDC = offeringPC.createDataChannel(
@@ -571,7 +601,8 @@ public class PeerConnectionTest extends ActivityTestCase {
answeringExpectations.expectRenegotiationNeeded();
WeakReference<MediaStream> aLMS = addTracksToPC(
factory, answeringPC, videoSource, "answeredMediaStream",
- "answeredVideoTrack", "answeredAudioTrack", answeringExpectations);
+ "answeredVideoTrack", "answeredAudioTrack",
+ new ExpectedResolutionSetter(offeringExpectations));
sdpLatch = new SdpObserverLatch();
answeringPC.createAnswer(sdpLatch, new MediaConstraints());
@@ -688,16 +719,218 @@ public class PeerConnectionTest extends ActivityTestCase {
answeringExpectations.dataChannel.close();
offeringExpectations.dataChannel.close();
- // TODO(fischman) MOAR test ideas:
- // - Test that PC.removeStream() works; requires a second
- // createOffer/createAnswer dance.
- // - audit each place that uses |constraints| for specifying non-trivial
- // constraints (and ensure they're honored).
- // - test error cases
- // - ensure reasonable coverage of _jni.cc is achieved. Coverage is
- // extra-important because of all the free-text (class/method names, etc)
- // in JNI-style programming; make sure no typos!
- // - Test that shutdown mid-interaction is crash-free.
+ // Free the Java-land objects, collect them, and sleep a bit to make sure we
+ // don't get late-arrival crashes after the Java-land objects have been
+ // freed.
+ shutdownPC(offeringPC, offeringExpectations);
+ offeringPC = null;
+ shutdownPC(answeringPC, answeringExpectations);
+ answeringPC = null;
+ videoSource.dispose();
+ factory.dispose();
+ System.gc();
+ }
+
+ @MediumTest
+ public void testTrackRemoval() throws Exception {
+ // Allow loopback interfaces too since our Android devices often don't
+ // have those.
+ PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
+ options.networkIgnoreMask = 0;
+ PeerConnectionFactory factory = new PeerConnectionFactory(options);
+
+ MediaConstraints pcConstraints = new MediaConstraints();
+ pcConstraints.mandatory.add(
+ new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
+
+ LinkedList<PeerConnection.IceServer> iceServers =
+ new LinkedList<PeerConnection.IceServer>();
+ iceServers.add(new PeerConnection.IceServer(
+ "stun:stun.l.google.com:19302"));
+
+ ObserverExpectations offeringExpectations =
+ new ObserverExpectations("PCTest:offerer");
+ PeerConnection offeringPC = factory.createPeerConnection(
+ iceServers, pcConstraints, offeringExpectations);
+ assertNotNull(offeringPC);
+
+ ObserverExpectations answeringExpectations =
+ new ObserverExpectations("PCTest:answerer");
+ PeerConnection answeringPC = factory.createPeerConnection(
+ iceServers, pcConstraints, answeringExpectations);
+ assertNotNull(answeringPC);
+
+ // We want to use the same camera for offerer & answerer, so create it here
+ // instead of in addTracksToPC.
+ VideoSource videoSource = factory.createVideoSource(
+ VideoCapturerAndroid.create("", null), new MediaConstraints());
+
+ // Add offerer media stream.
+ offeringExpectations.expectRenegotiationNeeded();
+ WeakReference<MediaStream> oLMS = addTracksToPC(
+ factory, offeringPC, videoSource, "offeredMediaStream",
+ "offeredVideoTrack", "offeredAudioTrack",
+ new ExpectedResolutionSetter(answeringExpectations));
+
+ // Create offer.
+ SdpObserverLatch sdpLatch = new SdpObserverLatch();
+ offeringPC.createOffer(sdpLatch, new MediaConstraints());
+ assertTrue(sdpLatch.await());
+ SessionDescription offerSdp = sdpLatch.getSdp();
+ assertEquals(offerSdp.type, SessionDescription.Type.OFFER);
+ assertFalse(offerSdp.description.isEmpty());
+
+ // Set local description for offerer.
+ sdpLatch = new SdpObserverLatch();
+ offeringExpectations.expectSignalingChange(SignalingState.HAVE_LOCAL_OFFER);
+ offeringExpectations.expectIceCandidates(2);
+ offeringExpectations.expectIceGatheringChange(IceGatheringState.COMPLETE);
+ offeringPC.setLocalDescription(sdpLatch, offerSdp);
+ assertTrue(sdpLatch.await());
+ assertNull(sdpLatch.getSdp());
+
+ // Set remote description for answerer.
+ sdpLatch = new SdpObserverLatch();
+ answeringExpectations.expectSignalingChange(
+ SignalingState.HAVE_REMOTE_OFFER);
+ answeringExpectations.expectAddStream("offeredMediaStream");
+ answeringPC.setRemoteDescription(sdpLatch, offerSdp);
+ assertTrue(sdpLatch.await());
+ assertNull(sdpLatch.getSdp());
+
+ // Add answerer media stream.
+ answeringExpectations.expectRenegotiationNeeded();
+ WeakReference<MediaStream> aLMS = addTracksToPC(
+ factory, answeringPC, videoSource, "answeredMediaStream",
+ "answeredVideoTrack", "answeredAudioTrack",
+ new ExpectedResolutionSetter(offeringExpectations));
+
+ // Create answer.
+ sdpLatch = new SdpObserverLatch();
+ answeringPC.createAnswer(sdpLatch, new MediaConstraints());
+ assertTrue(sdpLatch.await());
+ SessionDescription answerSdp = sdpLatch.getSdp();
+ assertEquals(answerSdp.type, SessionDescription.Type.ANSWER);
+ assertFalse(answerSdp.description.isEmpty());
+
+ // Set local description for answerer.
+ sdpLatch = new SdpObserverLatch();
+ answeringExpectations.expectSignalingChange(SignalingState.STABLE);
+ answeringExpectations.expectIceCandidates(2);
+ answeringExpectations.expectIceGatheringChange(IceGatheringState.COMPLETE);
+ answeringPC.setLocalDescription(sdpLatch, answerSdp);
+ assertTrue(sdpLatch.await());
+ assertNull(sdpLatch.getSdp());
+
+ // Set remote description for offerer.
+ sdpLatch = new SdpObserverLatch();
+ offeringExpectations.expectSignalingChange(SignalingState.STABLE);
+ offeringExpectations.expectAddStream("answeredMediaStream");
+
+ offeringExpectations.expectIceConnectionChange(
+ IceConnectionState.CHECKING);
+ offeringExpectations.expectIceConnectionChange(
+ IceConnectionState.CONNECTED);
+ // TODO(bemasc): uncomment once delivery of ICECompleted is reliable
+ // (https://code.google.com/p/webrtc/issues/detail?id=3021).
+ //
+ // offeringExpectations.expectIceConnectionChange(
+ // IceConnectionState.COMPLETED);
+ answeringExpectations.expectIceConnectionChange(
+ IceConnectionState.CHECKING);
+ answeringExpectations.expectIceConnectionChange(
+ IceConnectionState.CONNECTED);
+
+ offeringPC.setRemoteDescription(sdpLatch, answerSdp);
+ assertTrue(sdpLatch.await());
+ assertNull(sdpLatch.getSdp());
+
+ // Wait for at least one ice candidate from the offering PC and forward them to the answering
+ // PC.
+ for (IceCandidate candidate : offeringExpectations.getAtLeastOneIceCandidate()) {
+ answeringPC.addIceCandidate(candidate);
+ }
+
+ // Wait for at least one ice candidate from the answering PC and forward them to the offering
+ // PC.
+ for (IceCandidate candidate : answeringExpectations.getAtLeastOneIceCandidate()) {
+ offeringPC.addIceCandidate(candidate);
+ }
+
+ // Wait for one frame of the correct size to be delivered.
+ // Otherwise we could get a dummy black frame of unexpcted size when the
+ // video track is removed.
+ offeringExpectations.expectFramesDelivered(1);
+ answeringExpectations.expectFramesDelivered(1);
+
+ assertTrue(offeringExpectations.waitForAllExpectationsToBeSatisfied(TIMEOUT_SECONDS));
+ assertTrue(answeringExpectations.waitForAllExpectationsToBeSatisfied(TIMEOUT_SECONDS));
+
+ assertEquals(
+ PeerConnection.SignalingState.STABLE, offeringPC.signalingState());
+ assertEquals(
+ PeerConnection.SignalingState.STABLE, answeringPC.signalingState());
+
+ // Now do another negotiation, removing the video track from one peer.
+ // This previously caused a crash on pc.dispose().
+ // See: https://bugs.chromium.org/p/webrtc/issues/detail?id=5128
+ VideoTrack offererVideoTrack = oLMS.get().videoTracks.get(0);
+ // Note that when we call removeTrack, we regain responsibility for
+ // disposing of the track.
+ oLMS.get().removeTrack(offererVideoTrack);
+
+ // Create offer.
+ sdpLatch = new SdpObserverLatch();
+ offeringPC.createOffer(sdpLatch, new MediaConstraints());
+ assertTrue(sdpLatch.await());
+ offerSdp = sdpLatch.getSdp();
+ assertEquals(offerSdp.type, SessionDescription.Type.OFFER);
+ assertFalse(offerSdp.description.isEmpty());
+
+ // Set local description for offerer.
+ sdpLatch = new SdpObserverLatch();
+ offeringExpectations.expectSignalingChange(SignalingState.HAVE_LOCAL_OFFER);
+ offeringPC.setLocalDescription(sdpLatch, offerSdp);
+ assertTrue(sdpLatch.await());
+ assertNull(sdpLatch.getSdp());
+
+ // Set remote description for answerer.
+ sdpLatch = new SdpObserverLatch();
+ answeringExpectations.expectSignalingChange(
+ SignalingState.HAVE_REMOTE_OFFER);
+ answeringPC.setRemoteDescription(sdpLatch, offerSdp);
+ assertTrue(sdpLatch.await());
+ assertNull(sdpLatch.getSdp());
+
+ // Create answer.
+ sdpLatch = new SdpObserverLatch();
+ answeringPC.createAnswer(sdpLatch, new MediaConstraints());
+ assertTrue(sdpLatch.await());
+ answerSdp = sdpLatch.getSdp();
+ assertEquals(answerSdp.type, SessionDescription.Type.ANSWER);
+ assertFalse(answerSdp.description.isEmpty());
+
+ // Set local description for answerer.
+ sdpLatch = new SdpObserverLatch();
+ answeringExpectations.expectSignalingChange(SignalingState.STABLE);
+ answeringPC.setLocalDescription(sdpLatch, answerSdp);
+ assertTrue(sdpLatch.await());
+ assertNull(sdpLatch.getSdp());
+
+ // Set remote description for offerer.
+ sdpLatch = new SdpObserverLatch();
+ offeringExpectations.expectSignalingChange(SignalingState.STABLE);
+ offeringPC.setRemoteDescription(sdpLatch, answerSdp);
+ assertTrue(sdpLatch.await());
+ assertNull(sdpLatch.getSdp());
+
+ // Make sure the track was really removed.
+ // TODO(deadbeef): Currently the expectation is that the video track's
+ // state will be set to "ended". However, in the future, it's likely that
+ // the video track will be completely removed from the remote stream
+ // (as it is on the C++ level).
+ MediaStream aRMS = answeringExpectations.gotRemoteStreams.iterator().next();
+ assertEquals(aRMS.videoTracks.get(0).state(), MediaStreamTrack.State.ENDED);
// Free the Java-land objects, collect them, and sleep a bit to make sure we
// don't get late-arrival crashes after the Java-land objects have been
@@ -706,6 +939,7 @@ public class PeerConnectionTest extends ActivityTestCase {
offeringPC = null;
shutdownPC(answeringPC, answeringExpectations);
answeringPC = null;
+ offererVideoTrack.dispose();
videoSource.dispose();
factory.dispose();
System.gc();
@@ -713,8 +947,10 @@ public class PeerConnectionTest extends ActivityTestCase {
private static void shutdownPC(
PeerConnection pc, ObserverExpectations expectations) {
- expectations.dataChannel.unregisterObserver();
- expectations.dataChannel.dispose();
+ if (expectations.dataChannel != null) {
+ expectations.dataChannel.unregisterObserver();
+ expectations.dataChannel.dispose();
+ }
expectations.expectStatsCallback();
assertTrue(pc.getStats(expectations, null));
assertTrue(expectations.waitForAllExpectationsToBeSatisfied(TIMEOUT_SECONDS));
« no previous file with comments | « no previous file | webrtc/api/java/src/org/webrtc/MediaStream.java » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698