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