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.media.AudioManager; 26 import android.os.Environment; 27 import android.util.Log; 28 import android.view.OrientationEventListener; 29 import android.view.SurfaceView; 30 import java.io.File; 31 32 public 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