Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / third_party / webrtc / examples / android / media_demo / src / org / webrtc / webrtcdemo / MediaEngine.java
1 /*
2  *  Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
3  *
4  *  Use of this source code is governed by a BSD-style license
5  *  that can be found in the LICENSE file in the root of the source
6  *  tree. An additional intellectual property rights grant can be found
7  *  in the file PATENTS.  All contributing project authors may
8  *  be found in the AUTHORS file in the root of the source tree.
9  */
10
11 package org.webrtc.webrtcdemo;
12
13 import org.webrtc.videoengine.ViERenderer;
14 import org.webrtc.videoengine.VideoCaptureAndroid;
15
16 import android.app.AlertDialog;
17 import android.content.BroadcastReceiver;
18 import android.content.Context;
19 import android.content.DialogInterface;
20 import android.content.Intent;
21 import android.content.IntentFilter;
22 import android.hardware.Camera.CameraInfo;
23 import android.hardware.Camera;
24 import android.hardware.SensorManager;
25 import android.os.Environment;
26 import android.util.Log;
27 import android.view.OrientationEventListener;
28 import android.view.SurfaceView;
29 import java.io.File;
30
31 public class MediaEngine implements VideoDecodeEncodeObserver {
32   // TODO(henrike): Most of these should be moved to xml (since static).
33   private static final int VCM_VP8_PAYLOAD_TYPE = 100;
34   private static final int SEND_CODEC_FPS = 30;
35   // TODO(henrike): increase INIT_BITRATE_KBPS to 2000 and ensure that
36   // 720p30fps can be acheived (on hardware that can handle it). Note that
37   // setting 2000 currently leads to failure, so that has to be resolved first.
38   private static final int INIT_BITRATE_KBPS = 500;
39   private static final int MAX_BITRATE_KBPS = 3000;
40   private static final String LOG_DIR = "webrtc";
41   private static final int WIDTH_IDX = 0;
42   private static final int HEIGHT_IDX = 1;
43   private static final int[][] RESOLUTIONS = {
44     {176,144}, {320,240}, {352,288}, {640,480}, {1280,720}
45   };
46   // Arbitrary choice of 4/5 volume (204/256).
47   private static final int volumeLevel = 204;
48
49   public static int numberOfResolutions() { return RESOLUTIONS.length; }
50
51   public static String[] resolutionsAsString() {
52     String[] retVal = new String[numberOfResolutions()];
53     for (int i = 0; i < numberOfResolutions(); ++i) {
54       retVal[i] = RESOLUTIONS[i][0] + "x" + RESOLUTIONS[i][1];
55     }
56     return retVal;
57   }
58
59   // Checks for and communicate failures to user (logcat and popup).
60   private void check(boolean value, String message) {
61     if (value) {
62       return;
63     }
64     Log.e("WEBRTC-CHECK", message);
65     AlertDialog alertDialog = new AlertDialog.Builder(context).create();
66     alertDialog.setTitle("WebRTC Error");
67     alertDialog.setMessage(message);
68     alertDialog.setButton(DialogInterface.BUTTON_POSITIVE,
69         "OK",
70         new DialogInterface.OnClickListener() {
71           public void onClick(DialogInterface dialog, int which) {
72             dialog.dismiss();
73             return;
74           }
75         }
76                           );
77     alertDialog.show();
78   }
79
80   // Converts device rotation to camera rotation. Rotation depends on if the
81   // camera is back facing and rotate with the device or front facing and
82   // rotating in the opposite direction of the device.
83   private static int rotationFromRealWorldUp(CameraInfo info,
84                                              int deviceRotation) {
85     int coarseDeviceOrientation =
86         (int)(Math.round((double)deviceRotation / 90) * 90) % 360;
87     if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
88       // The front camera rotates in the opposite direction of the
89       // device.
90       int inverseDeviceOrientation = 360 - coarseDeviceOrientation;
91       return (inverseDeviceOrientation + info.orientation) % 360;
92     }
93     return (coarseDeviceOrientation + info.orientation) % 360;
94   }
95
96   // Shared Audio/Video members.
97   private final Context context;
98   private String remoteIp;
99   private boolean enableTrace;
100
101     // Audio
102   private VoiceEngine voe;
103   private int audioChannel;
104   private boolean audioEnabled;
105   private boolean voeRunning;
106   private int audioCodecIndex;
107   private int audioTxPort;
108   private int audioRxPort;
109
110   private boolean speakerEnabled;
111   private boolean headsetPluggedIn;
112   private boolean enableAgc;
113   private boolean enableNs;
114   private boolean enableAecm;
115
116   private BroadcastReceiver headsetListener;
117
118   private boolean audioRtpDump;
119   private boolean apmRecord;
120
121   // Video
122   private VideoEngine vie;
123   private int videoChannel;
124   private boolean receiveVideo;
125   private boolean sendVideo;
126   private boolean vieRunning;
127   private int videoCodecIndex;
128   private int resolutionIndex;
129   private int videoTxPort;
130   private int videoRxPort;
131
132   // Indexed by CameraInfo.CAMERA_FACING_{BACK,FRONT}.
133   private CameraInfo cameras[];
134   private boolean useFrontCamera;
135   private int currentCameraHandle;
136   private boolean enableNack;
137   // openGl, surfaceView or mediaCodec (integers.xml)
138   private int viewSelection;
139   private boolean videoRtpDump;
140
141   private SurfaceView svLocal;
142   private SurfaceView svRemote;
143   MediaCodecVideoDecoder externalCodec;
144
145   private int inFps;
146   private int inKbps;
147   private int outFps;
148   private int outKbps;
149   private int inWidth;
150   private int inHeight;
151
152   private OrientationEventListener orientationListener;
153   private int deviceOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
154
155   public MediaEngine(Context context) {
156     this.context = context;
157     voe = new VoiceEngine();
158     check(voe.init() == 0, "Failed voe Init");
159     audioChannel = voe.createChannel();
160     check(audioChannel >= 0, "Failed voe CreateChannel");
161     vie = new VideoEngine();
162     check(vie.init() == 0, "Failed voe Init");
163     check(vie.setVoiceEngine(voe) == 0, "Failed setVoiceEngine");
164     videoChannel = vie.createChannel();
165     check(audioChannel >= 0, "Failed voe CreateChannel");
166     check(vie.connectAudioChannel(videoChannel, audioChannel) == 0,
167         "Failed ConnectAudioChannel");
168
169     cameras = new CameraInfo[2];
170     CameraInfo info = new CameraInfo();
171     for (int i = 0; i < Camera.getNumberOfCameras(); ++i) {
172       Camera.getCameraInfo(i, info);
173       cameras[info.facing] = info;
174     }
175     setDefaultCamera();
176     check(voe.setSpeakerVolume(volumeLevel) == 0,
177         "Failed setSpeakerVolume");
178     check(voe.setAecmMode(VoiceEngine.AecmModes.SPEAKERPHONE, false) == 0,
179         "VoE set Aecm speakerphone mode failed");
180     check(vie.setKeyFrameRequestMethod(videoChannel,
181             VideoEngine.VieKeyFrameRequestMethod.
182             KEY_FRAME_REQUEST_PLI_RTCP) == 0,
183         "Failed setKeyFrameRequestMethod");
184     check(vie.registerObserver(videoChannel, this) == 0,
185         "Failed registerObserver");
186
187     // TODO(hellner): SENSOR_DELAY_NORMAL?
188     // Listen to changes in device orientation.
189     orientationListener =
190         new OrientationEventListener(context, SensorManager.SENSOR_DELAY_UI) {
191           public void onOrientationChanged (int orientation) {
192             deviceOrientation = orientation;
193             compensateRotation();
194           }
195         };
196     orientationListener.enable();
197     // Listen to headset being plugged in/out.
198     IntentFilter receiverFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG);
199     headsetListener = new BroadcastReceiver() {
200         @Override
201         public void onReceive(Context context, Intent intent) {
202           if (intent.getAction().compareTo(Intent.ACTION_HEADSET_PLUG) == 0) {
203             headsetPluggedIn = intent.getIntExtra("state", 0) == 1;
204             updateAudioOutput();
205           }
206         }
207       };
208     context.registerReceiver(headsetListener, receiverFilter);
209   }
210
211   public void dispose() {
212     check(!voeRunning && !voeRunning, "Engines must be stopped before dispose");
213     context.unregisterReceiver(headsetListener);
214     orientationListener.disable();
215     check(vie.deregisterObserver(videoChannel) == 0,
216         "Failed deregisterObserver");
217     if (externalCodec != null) {
218       check(vie.deRegisterExternalReceiveCodec(videoChannel,
219               VCM_VP8_PAYLOAD_TYPE) == 0,
220           "Failed to deregister external decoder");
221       externalCodec = null;
222     }
223     check(vie.deleteChannel(videoChannel) == 0, "DeleteChannel");
224     vie.dispose();
225     check(voe.deleteChannel(audioChannel) == 0, "VoE delete channel failed");
226     voe.dispose();
227   }
228
229   public void start() {
230     if (audioEnabled) {
231       startVoE();
232     }
233     if (receiveVideo || sendVideo) {
234       startViE();
235     }
236   }
237
238   public void stop() {
239     stopVoe();
240     stopVie();
241   }
242
243   public boolean isRunning() {
244     return voeRunning || vieRunning;
245   }
246
247   public void setRemoteIp(String remoteIp) {
248     this.remoteIp = remoteIp;
249     UpdateSendDestination();
250   }
251
252   public String remoteIp() { return remoteIp; }
253
254   public void setTrace(boolean enable) {
255     if (enable) {
256       vie.setTraceFile("/sdcard/trace.txt", false);
257       vie.setTraceFilter(VideoEngine.TraceLevel.TRACE_ERROR);
258       return;
259     }
260     vie.setTraceFilter(VideoEngine.TraceLevel.TRACE_NONE);
261   }
262
263   private String getDebugDirectory() {
264     // Should create a folder in /scard/|LOG_DIR|
265     return Environment.getExternalStorageDirectory().toString() + "/" +
266         LOG_DIR;
267   }
268
269   private boolean createDebugDirectory() {
270     File webrtc_dir = new File(getDebugDirectory());
271     if (!webrtc_dir.exists()) {
272       return webrtc_dir.mkdir();
273     }
274     return webrtc_dir.isDirectory();
275   }
276
277   public void startVoE() {
278     check(!voeRunning, "VoE already started");
279     check(voe.startListen(audioChannel) == 0, "Failed StartListen");
280     check(voe.startPlayout(audioChannel) == 0, "VoE start playout failed");
281     check(voe.startSend(audioChannel) == 0, "VoE start send failed");
282     voeRunning = true;
283   }
284
285   private void stopVoe() {
286     check(voeRunning, "VoE not started");
287     check(voe.stopSend(audioChannel) == 0, "VoE stop send failed");
288     check(voe.stopPlayout(audioChannel) == 0, "VoE stop playout failed");
289     check(voe.stopListen(audioChannel) == 0, "VoE stop listen failed");
290     voeRunning = false;
291   }
292
293   public void setAudio(boolean audioEnabled) {
294     this.audioEnabled = audioEnabled;
295   }
296
297   public boolean audioEnabled() { return audioEnabled; }
298
299   public int audioCodecIndex() { return audioCodecIndex; }
300
301   public void setAudioCodec(int codecNumber) {
302     audioCodecIndex = codecNumber;
303     CodecInst codec = voe.getCodec(codecNumber);
304     check(voe.setSendCodec(audioChannel, codec) == 0, "Failed setSendCodec");
305     codec.dispose();
306   }
307
308   public String[] audioCodecsAsString() {
309     String[] retVal = new String[voe.numOfCodecs()];
310     for (int i = 0; i < voe.numOfCodecs(); ++i) {
311       CodecInst codec = voe.getCodec(i);
312       retVal[i] = codec.toString();
313       codec.dispose();
314     }
315     return retVal;
316   }
317
318   private CodecInst[] defaultAudioCodecs() {
319     CodecInst[] retVal = new CodecInst[voe.numOfCodecs()];
320      for (int i = 0; i < voe.numOfCodecs(); ++i) {
321       retVal[i] = voe.getCodec(i);
322     }
323     return retVal;
324   }
325
326   public int getIsacIndex() {
327     CodecInst[] codecs = defaultAudioCodecs();
328     for (int i = 0; i < codecs.length; ++i) {
329       if (codecs[i].name().contains("ISAC")) {
330         return i;
331       }
332     }
333     return 0;
334   }
335
336   public void setAudioTxPort(int audioTxPort) {
337     this.audioTxPort = audioTxPort;
338     UpdateSendDestination();
339   }
340
341   public int audioTxPort() { return audioTxPort; }
342
343   public void setAudioRxPort(int audioRxPort) {
344     check(voe.setLocalReceiver(audioChannel, audioRxPort) == 0,
345         "Failed setLocalReceiver");
346     this.audioRxPort = audioRxPort;
347   }
348
349   public int audioRxPort() { return audioRxPort; }
350
351   public boolean agcEnabled() { return enableAgc; }
352
353   public void setAgc(boolean enable) {
354     enableAgc = enable;
355     VoiceEngine.AgcConfig agc_config =
356         new VoiceEngine.AgcConfig(3, 9, true);
357     check(voe.setAgcConfig(agc_config) == 0, "VoE set AGC Config failed");
358     check(voe.setAgcStatus(enableAgc, VoiceEngine.AgcModes.FIXED_DIGITAL) == 0,
359         "VoE set AGC Status failed");
360   }
361
362   public boolean nsEnabled() { return enableNs; }
363
364   public void setNs(boolean enable) {
365     enableNs = enable;
366     check(voe.setNsStatus(enableNs,
367             VoiceEngine.NsModes.MODERATE_SUPPRESSION) == 0,
368         "VoE set NS Status failed");
369   }
370
371   public boolean aecmEnabled() { return enableAecm; }
372
373   public void setEc(boolean enable) {
374     enableAecm = enable;
375     check(voe.setEcStatus(enable, VoiceEngine.EcModes.AECM) == 0,
376         "voe setEcStatus");
377   }
378
379   public boolean speakerEnabled() {
380     return speakerEnabled;
381   }
382
383   public void setSpeaker(boolean enable) {
384     speakerEnabled = enable;
385     updateAudioOutput();
386   }
387
388   // Debug helpers.
389   public boolean apmRecord() { return apmRecord; }
390
391   public boolean audioRtpDump() { return audioRtpDump; }
392
393   public void setDebuging(boolean enable) {
394     apmRecord = enable;
395     if (!enable) {
396       check(voe.stopDebugRecording() == 0, "Failed stopping debug");
397       return;
398     }
399     if (!createDebugDirectory()) {
400       check(false, "Unable to create debug directory.");
401       return;
402     }
403     String debugDirectory = getDebugDirectory();
404     check(voe.startDebugRecording(debugDirectory +  String.format("/apm_%d.dat",
405                 System.currentTimeMillis())) == 0,
406         "Failed starting debug");
407   }
408
409   public void setIncomingVoeRtpDump(boolean enable) {
410     audioRtpDump = enable;
411     if (!enable) {
412       check(voe.stopRtpDump(videoChannel,
413               VoiceEngine.RtpDirections.INCOMING) == 0,
414           "voe stopping rtp dump");
415       return;
416     }
417     String debugDirectory = getDebugDirectory();
418     check(voe.startRtpDump(videoChannel, debugDirectory +
419             String.format("/voe_%d.rtp", System.currentTimeMillis()),
420             VoiceEngine.RtpDirections.INCOMING) == 0,
421         "voe starting rtp dump");
422   }
423
424   private void updateAudioOutput() {
425     boolean useSpeaker = !headsetPluggedIn && speakerEnabled;
426     check(voe.setLoudspeakerStatus(useSpeaker) == 0,
427         "Failed updating loudspeaker");
428   }
429
430   public void startViE() {
431     check(!vieRunning, "ViE already started");
432
433     if (receiveVideo) {
434       if (viewSelection ==
435           context.getResources().getInteger(R.integer.openGl)) {
436         svRemote = ViERenderer.CreateRenderer(context, true);
437       } else if (viewSelection ==
438           context.getResources().getInteger(R.integer.surfaceView)) {
439         svRemote = ViERenderer.CreateRenderer(context, false);
440       } else {
441         externalCodec = new MediaCodecVideoDecoder(context);
442         svRemote = externalCodec.getView();
443       }
444       if (externalCodec != null) {
445         check(vie.registerExternalReceiveCodec(videoChannel,
446                 VCM_VP8_PAYLOAD_TYPE, externalCodec, true) == 0,
447             "Failed to register external decoder");
448       } else {
449         check(vie.addRenderer(videoChannel, svRemote,
450                 0, 0, 0, 1, 1) == 0, "Failed AddRenderer");
451         check(vie.startRender(videoChannel) == 0, "Failed StartRender");
452       }
453       check(vie.startReceive(videoChannel) == 0, "Failed StartReceive");
454     }
455     if (sendVideo) {
456       startCamera();
457       check(vie.startSend(videoChannel) == 0, "Failed StartSend");
458     }
459     vieRunning = true;
460   }
461
462   private void stopVie() {
463     if (!vieRunning) {
464       return;
465     }
466     check(vie.stopSend(videoChannel) == 0, "StopSend");
467     stopCamera();
468     check(vie.stopReceive(videoChannel) == 0, "StopReceive");
469     if (externalCodec != null) {
470       check(vie.deRegisterExternalReceiveCodec(videoChannel,
471               VCM_VP8_PAYLOAD_TYPE) == 0,
472               "Failed to deregister external decoder");
473       externalCodec.dispose();
474       externalCodec = null;
475     } else {
476       check(vie.stopRender(videoChannel) == 0, "StopRender");
477       check(vie.removeRenderer(videoChannel) == 0, "RemoveRenderer");
478     }
479     svRemote = null;
480     vieRunning = false;
481   }
482
483   public void setReceiveVideo(boolean receiveVideo) {
484     this.receiveVideo = receiveVideo;
485   }
486
487   public boolean receiveVideo() { return receiveVideo; }
488
489   public void setSendVideo(boolean sendVideo) { this.sendVideo = sendVideo; }
490
491   public boolean sendVideo() { return sendVideo; }
492
493   public int videoCodecIndex() { return videoCodecIndex; }
494
495   public void setVideoCodec(int codecNumber) {
496     videoCodecIndex = codecNumber;
497     updateVideoCodec();
498   }
499
500   public String[] videoCodecsAsString() {
501     String[] retVal = new String[vie.numberOfCodecs()];
502     for (int i = 0; i < vie.numberOfCodecs(); ++i) {
503       VideoCodecInst codec = vie.getCodec(i);
504       retVal[i] = codec.toString();
505       codec.dispose();
506     }
507     return retVal;
508   }
509
510   public int resolutionIndex() { return resolutionIndex; }
511
512   public void setResolutionIndex(int resolution) {
513     resolutionIndex = resolution;
514     updateVideoCodec();
515   }
516
517   private void updateVideoCodec() {
518     VideoCodecInst codec = getVideoCodec(videoCodecIndex, resolutionIndex);
519     check(vie.setSendCodec(videoChannel, codec) == 0, "Failed setReceiveCodec");
520     codec.dispose();
521   }
522
523   private VideoCodecInst getVideoCodec(int codecNumber, int resolution) {
524     VideoCodecInst retVal = vie.getCodec(codecNumber);
525     retVal.setStartBitRate(INIT_BITRATE_KBPS);
526     retVal.setMaxBitRate(MAX_BITRATE_KBPS);
527     retVal.setWidth(RESOLUTIONS[resolution][WIDTH_IDX]);
528     retVal.setHeight(RESOLUTIONS[resolution][HEIGHT_IDX]);
529     retVal.setMaxFrameRate(SEND_CODEC_FPS);
530     return retVal;
531   }
532
533   public void setVideoRxPort(int videoRxPort) {
534     this.videoRxPort = videoRxPort;
535     check(vie.setLocalReceiver(videoChannel, videoRxPort) == 0,
536         "Failed setLocalReceiver");
537   }
538
539   public int videoRxPort() { return videoRxPort; }
540
541   public void setVideoTxPort(int videoTxPort) {
542     this.videoTxPort = videoTxPort;
543     UpdateSendDestination();
544   }
545
546   private void UpdateSendDestination() {
547     if (remoteIp == null) {
548       return;
549     }
550     if (audioTxPort != 0) {
551       check(voe.setSendDestination(audioChannel, audioTxPort,
552               remoteIp) == 0, "VoE set send destination failed");
553     }
554     if (videoTxPort != 0) {
555       check(vie.setSendDestination(videoChannel, videoTxPort, remoteIp) == 0,
556           "Failed setSendDestination");
557     }
558   }
559
560   public int videoTxPort() {
561     return videoTxPort;
562   }
563
564   public boolean hasMultipleCameras() {
565     return Camera.getNumberOfCameras() > 1;
566   }
567
568   public boolean frontCameraIsSet() {
569     return useFrontCamera;
570   }
571
572   // Set default camera to front if there is a front camera.
573   private void setDefaultCamera() {
574     useFrontCamera = hasFrontCamera();
575   }
576
577   public void toggleCamera() {
578     if (vieRunning) {
579       stopCamera();
580     }
581     useFrontCamera = !useFrontCamera;
582     if (vieRunning) {
583       startCamera();
584     }
585   }
586
587   private void startCamera() {
588     CameraDesc cameraInfo = vie.getCaptureDevice(getCameraId(getCameraIndex()));
589     currentCameraHandle = vie.allocateCaptureDevice(cameraInfo);
590     cameraInfo.dispose();
591     check(vie.connectCaptureDevice(currentCameraHandle, videoChannel) == 0,
592         "Failed to connect capture device");
593     // Camera and preview surface.
594     svLocal = new SurfaceView(context);
595     VideoCaptureAndroid.setLocalPreview(svLocal.getHolder());
596     check(vie.startCapture(currentCameraHandle) == 0, "Failed StartCapture");
597     compensateRotation();
598   }
599
600   private void stopCamera() {
601     check(vie.stopCapture(currentCameraHandle) == 0, "Failed StopCapture");
602     svLocal = null;
603     check(vie.releaseCaptureDevice(currentCameraHandle) == 0,
604         "Failed ReleaseCaptureDevice");
605   }
606
607   private boolean hasFrontCamera() {
608     return cameras[CameraInfo.CAMERA_FACING_FRONT] != null;
609   }
610
611   public SurfaceView getRemoteSurfaceView() {
612     return svRemote;
613   }
614
615   public SurfaceView getLocalSurfaceView() {
616     return svLocal;
617   }
618
619   public void setViewSelection(int viewSelection) {
620     this.viewSelection = viewSelection;
621   }
622
623   public int viewSelection() { return viewSelection; }
624
625   public boolean nackEnabled() { return enableNack; }
626
627   public void setNack(boolean enable) {
628     enableNack = enable;
629     check(vie.setNackStatus(videoChannel, enableNack) == 0,
630         "Failed setNackStatus");
631   }
632
633   // Collates current state into a multiline string.
634   public String sendReceiveState() {
635     int packetLoss = 0;
636     if (vieRunning) {
637       RtcpStatistics stats = vie.getReceivedRtcpStatistics(videoChannel);
638       if (stats != null) {
639         // Calculate % lost from fraction lost.
640         // Definition of fraction lost can be found in RFC3550.
641         packetLoss = (stats.fractionLost * 100) >> 8;
642       }
643     }
644     String retVal =
645         "fps in/out: " + inFps + "/" + outFps + "\n" +
646         "kBps in/out: " + inKbps / 1024 + "/ " + outKbps / 1024 + "\n" +
647         "resolution: " + inWidth + "x" + inHeight + "\n" +
648         "loss: " + packetLoss + "%";
649     return retVal;
650   }
651
652   MediaEngineObserver observer;
653   public void setObserver(MediaEngineObserver observer) {
654     this.observer = observer;
655   }
656
657   // Callbacks from the VideoDecodeEncodeObserver interface.
658   public void incomingRate(int videoChannel, int framerate, int bitrate) {
659     inFps = framerate;
660     inKbps = bitrate;
661     newStats();
662   }
663
664   public void incomingCodecChanged(int videoChannel,
665       VideoCodecInst videoCodec) {
666     inWidth = videoCodec.width();
667     inHeight = videoCodec.height();
668     videoCodec.dispose();
669     newStats();
670   }
671
672   public void requestNewKeyFrame(int videoChannel) {}
673
674   public void outgoingRate(int videoChannel, int framerate, int bitrate) {
675     outFps = framerate;
676     outKbps = bitrate;
677     newStats();
678   }
679
680   private void newStats() {
681     if (observer != null) {
682       observer.newStats(sendReceiveState());
683     }
684   }
685
686   // Debug helpers.
687   public boolean videoRtpDump() { return videoRtpDump; }
688
689   public void setIncomingVieRtpDump(boolean enable) {
690     videoRtpDump = enable;
691     if (!enable) {
692       check(vie.stopRtpDump(videoChannel,
693               VideoEngine.RtpDirections.INCOMING) == 0,
694           "vie StopRTPDump");
695       return;
696     }
697     String debugDirectory = getDebugDirectory();
698     check(vie.startRtpDump(videoChannel, debugDirectory +
699             String.format("/vie_%d.rtp", System.currentTimeMillis()),
700             VideoEngine.RtpDirections.INCOMING) == 0,
701         "vie StartRtpDump");
702   }
703
704   private int getCameraIndex() {
705     return useFrontCamera ? Camera.CameraInfo.CAMERA_FACING_FRONT :
706         Camera.CameraInfo.CAMERA_FACING_BACK;
707   }
708
709   private int getCameraId(int index) {
710     for (int i = Camera.getNumberOfCameras() - 1; i >= 0; --i) {
711       CameraInfo info = new CameraInfo();
712       Camera.getCameraInfo(i, info);
713       if (index == info.facing) {
714         return i;
715       }
716     }
717     throw new RuntimeException("Index does not match a camera");
718   }
719
720   private void compensateRotation() {
721     if (svLocal == null) {
722       // Not rendering (or sending).
723       return;
724     }
725     if (deviceOrientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
726       return;
727     }
728     int cameraRotation = rotationFromRealWorldUp(
729         cameras[getCameraIndex()], deviceOrientation);
730     // Egress streams should have real world up as up.
731     check(
732         vie.setRotateCapturedFrames(currentCameraHandle, cameraRotation) == 0,
733         "Failed setRotateCapturedFrames: camera " + currentCameraHandle +
734         "rotation " + cameraRotation);
735   }
736 }