ApprtDemo Android: Switch between front and back camera.

This adds a UI icon for switching between the front and back camera.
This cl adds the possibility to change between the front and back camera while in a call
or before the other end have connected.

BUG=3786
R=glaznev@webrtc.org

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

git-svn-id: http://webrtc.googlecode.com/svn/trunk@7553 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
perkj@webrtc.org
2014-10-29 08:10:03 +00:00
parent 663fdd02fd
commit 7998089789
4 changed files with 121 additions and 16 deletions

View File

@@ -18,6 +18,13 @@
<!-- TODO(kjellander): Add audio and video mute buttons. --> <!-- TODO(kjellander): Add audio and video mute buttons. -->
<ImageButton
android:id="@+id/button_switch_camera"
android:background="@android:drawable/ic_menu_camera"
android:contentDescription="@string/switch_camera"
android:layout_width="48dp"
android:layout_height="48dp"/>
<ImageButton <ImageButton
android:id="@+id/button_toggle_debug" android:id="@+id/button_toggle_debug"
android:background="@android:drawable/ic_menu_info_details" android:background="@android:drawable/ic_menu_info_details"

View File

@@ -16,6 +16,7 @@
<string name="connecting_to">Connecting to: %1$s</string> <string name="connecting_to">Connecting to: %1$s</string>
<string name="missing_url">FATAL ERROR: Missing URL to connect to.</string> <string name="missing_url">FATAL ERROR: Missing URL to connect to.</string>
<string name="ok">OK</string> <string name="ok">OK</string>
<string name="switch_camera">Switch front/back camera</string>
<string name="action_settings">Settings</string> <string name="action_settings">Settings</string>
<!-- Settings strings. --> <!-- Settings strings. -->

View File

@@ -139,6 +139,14 @@ public class AppRTCDemoActivity extends Activity
} }
}); });
((ImageButton) findViewById(R.id.button_switch_camera)).setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View view) {
pc.switchCamera();
}
});
((ImageButton) findViewById(R.id.button_toggle_debug)).setOnClickListener( ((ImageButton) findViewById(R.id.button_toggle_debug)).setOnClickListener(
new View.OnClickListener() { new View.OnClickListener() {
@Override @Override

View File

@@ -60,13 +60,17 @@ public class PeerConnectionClient {
private boolean videoSourceStopped; private boolean videoSourceStopped;
private final PCObserver pcObserver = new PCObserver(); private final PCObserver pcObserver = new PCObserver();
private final SDPObserver sdpObserver = new SDPObserver(); private final SDPObserver sdpObserver = new SDPObserver();
private final MediaConstraints videoConstraints;
private final VideoRenderer.Callbacks localRender;
private final VideoRenderer.Callbacks remoteRender; private final VideoRenderer.Callbacks remoteRender;
private LinkedList<IceCandidate> queuedRemoteCandidates = private LinkedList<IceCandidate> queuedRemoteCandidates =
new LinkedList<IceCandidate>(); new LinkedList<IceCandidate>();
private MediaConstraints sdpMediaConstraints; private final MediaConstraints sdpMediaConstraints;
private PeerConnectionEvents events; private final PeerConnectionEvents events;
private boolean isInitiator; private boolean isInitiator;
private boolean useFrontFacingCamera = true;
private SessionDescription localSdp = null; // either offer or answer SDP private SessionDescription localSdp = null; // either offer or answer SDP
private MediaStream videoMediaStream = null;
public PeerConnectionClient( public PeerConnectionClient(
Activity activity, Activity activity,
@@ -75,6 +79,8 @@ public class PeerConnectionClient {
AppRTCSignalingParameters appRtcParameters, AppRTCSignalingParameters appRtcParameters,
PeerConnectionEvents events) { PeerConnectionEvents events) {
this.activity = activity; this.activity = activity;
this.videoConstraints = appRtcParameters.videoConstraints;
this.localRender = localRender;
this.remoteRender = remoteRender; this.remoteRender = remoteRender;
this.events = events; this.events = events;
isInitiator = appRtcParameters.initiator; isInitiator = appRtcParameters.initiator;
@@ -101,24 +107,20 @@ public class PeerConnectionClient {
// EnumSet.of(Logging.TraceLevel.TRACE_ALL), // EnumSet.of(Logging.TraceLevel.TRACE_ALL),
// Logging.Severity.LS_SENSITIVE); // Logging.Severity.LS_SENSITIVE);
Log.d(TAG, "Creating local video source"); if (videoConstraints != null) {
MediaStream lMS = factory.createLocalMediaStream("ARDAMS"); videoMediaStream = factory.createLocalMediaStream("ARDAMSVideo");
if (appRtcParameters.videoConstraints != null) { videoMediaStream.addTrack(createVideoTrack(useFrontFacingCamera));
VideoCapturer capturer = getVideoCapturer(); pc.addStream(videoMediaStream, new MediaConstraints());
videoSource = factory.createVideoSource(
capturer, appRtcParameters.videoConstraints);
VideoTrack videoTrack =
factory.createVideoTrack("ARDAMSv0", videoSource);
videoTrack.addRenderer(new VideoRenderer(localRender));
lMS.addTrack(videoTrack);
} }
if (appRtcParameters.audioConstraints != null) { if (appRtcParameters.audioConstraints != null) {
MediaStream lMS = factory.createLocalMediaStream("ARDAMSAudio");
lMS.addTrack(factory.createAudioTrack( lMS.addTrack(factory.createAudioTrack(
"ARDAMSa0", "ARDAMSa0",
factory.createAudioSource(appRtcParameters.audioConstraints))); factory.createAudioSource(appRtcParameters.audioConstraints)));
}
pc.addStream(lMS, new MediaConstraints()); pc.addStream(lMS, new MediaConstraints());
} }
}
public boolean getStats(StatsObserver observer, MediaStreamTrack track) { public boolean getStats(StatsObserver observer, MediaStreamTrack track) {
return pc.getStats(observer, track); return pc.getStats(observer, track);
@@ -202,11 +204,15 @@ public class PeerConnectionClient {
// Cycle through likely device names for the camera and return the first // Cycle through likely device names for the camera and return the first
// capturer that works, or crash if none do. // capturer that works, or crash if none do.
private VideoCapturer getVideoCapturer() { private VideoCapturer getVideoCapturer(boolean useFrontFacing) {
String[] cameraFacing = { "front", "back" }; String[] cameraFacing = { "front", "back" };
if (!useFrontFacing) {
cameraFacing[0] = "back";
cameraFacing[1] = "front";
}
for (String facing : cameraFacing) {
int[] cameraIndex = { 0, 1 }; int[] cameraIndex = { 0, 1 };
int[] cameraOrientation = { 0, 90, 180, 270 }; int[] cameraOrientation = { 0, 90, 180, 270 };
for (String facing : cameraFacing) {
for (int index : cameraIndex) { for (int index : cameraIndex) {
for (int orientation : cameraOrientation) { for (int orientation : cameraOrientation) {
String name = "Camera " + index + ", Facing " + facing + String name = "Camera " + index + ", Facing " + facing +
@@ -222,6 +228,22 @@ public class PeerConnectionClient {
throw new RuntimeException("Failed to open capturer"); throw new RuntimeException("Failed to open capturer");
} }
private VideoTrack createVideoTrack(boolean frontFacing) {
VideoCapturer capturer = getVideoCapturer(frontFacing);
if (videoSource != null) {
videoSource.stop();
videoSource.dispose();
}
videoSource = factory.createVideoSource(
capturer, videoConstraints);
String trackExtension = frontFacing ? "frontFacing" : "backFacing";
VideoTrack videoTrack =
factory.createVideoTrack("ARDAMSv0" + trackExtension, videoSource);
videoTrack.addRenderer(new VideoRenderer(localRender));
return videoTrack;
}
// Poor-man's assert(): die with |msg| unless |condition| is true. // Poor-man's assert(): die with |msg| unless |condition| is true.
private static void abortUnless(boolean condition, String msg) { private static void abortUnless(boolean condition, String msg) {
if (!condition) { if (!condition) {
@@ -285,6 +307,49 @@ public class PeerConnectionClient {
queuedRemoteCandidates = null; queuedRemoteCandidates = null;
} }
public void switchCamera() {
if (videoConstraints == null)
return; // No video is sent.
if (pc.signalingState() != PeerConnection.SignalingState.STABLE) {
Log.e(TAG, "Switching camera during negotiation is not handled.");
return;
}
pc.removeStream(videoMediaStream);
VideoTrack currentTrack = videoMediaStream.videoTracks.get(0);
videoMediaStream.removeTrack(currentTrack);
String trackId = currentTrack.id();
// On Android, there can only be one camera open at the time and we
// need to release our implicit references to the videoSource before the
// PeerConnectionFactory is released. Since createVideoTrack creates a new
// videoSource and frees the old one, we need to release the track here.
currentTrack.dispose();
useFrontFacingCamera = !useFrontFacingCamera;
VideoTrack newTrack = createVideoTrack(useFrontFacingCamera);
videoMediaStream.addTrack(newTrack);
pc.addStream(videoMediaStream, new MediaConstraints());
SessionDescription remoteDesc = pc.getRemoteDescription();
if (localSdp == null || remoteDesc == null) {
Log.d(TAG, "Switching camera before the negotiation started.");
return;
}
localSdp = new SessionDescription(localSdp.type,
localSdp.description.replaceAll(trackId, newTrack.id()));
if (isInitiator) {
pc.setLocalDescription(new SwitchCameraSdbObserver(), localSdp);
pc.setRemoteDescription(new SwitchCameraSdbObserver(), remoteDesc);
} else {
pc.setRemoteDescription(new SwitchCameraSdbObserver(), remoteDesc);
pc.setLocalDescription(new SwitchCameraSdbObserver(), localSdp);
}
}
// Implementation detail: observe ICE & stream changes and react accordingly. // Implementation detail: observe ICE & stream changes and react accordingly.
private class PCObserver implements PeerConnection.Observer { private class PCObserver implements PeerConnection.Observer {
@Override @Override
@@ -442,4 +507,28 @@ public class PeerConnectionClient {
}); });
} }
} }
private class SwitchCameraSdbObserver implements SdpObserver {
@Override
public void onCreateSuccess(SessionDescription sdp) {
}
@Override
public void onSetSuccess() {
Log.d(TAG, "Camera switch SDP set succesfully");
}
@Override
public void onCreateFailure(final String error) {
}
@Override
public void onSetFailure(final String error) {
activity.runOnUiThread(new Runnable() {
public void run() {
throw new RuntimeException("setSDP error while switching camera: " + error);
}
});
}
}
} }