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