| OLD | NEW | 
 | (Empty) | 
|    1 /* |  | 
|    2  *  Copyright 2015 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; |  | 
|   12  |  | 
|   13 import org.webrtc.CameraEnumerationAndroid.CaptureFormat; |  | 
|   14  |  | 
|   15 import android.content.Context; |  | 
|   16 import android.os.Handler; |  | 
|   17 import android.os.SystemClock; |  | 
|   18 import android.view.Surface; |  | 
|   19 import android.view.WindowManager; |  | 
|   20  |  | 
|   21 import java.io.IOException; |  | 
|   22 import java.nio.ByteBuffer; |  | 
|   23 import java.util.HashSet; |  | 
|   24 import java.util.List; |  | 
|   25 import java.util.Set; |  | 
|   26 import java.util.concurrent.CountDownLatch; |  | 
|   27 import java.util.concurrent.TimeUnit; |  | 
|   28  |  | 
|   29 // Android specific implementation of VideoCapturer. |  | 
|   30 // An instance of this class can be created by an application using |  | 
|   31 // VideoCapturerAndroid.create(); |  | 
|   32 // This class extends VideoCapturer with a method to easily switch between the |  | 
|   33 // front and back camera. It also provides methods for enumerating valid device |  | 
|   34 // names. |  | 
|   35 // |  | 
|   36 // Threading notes: this class is called from C++ code, Android Camera callbacks
     , and possibly |  | 
|   37 // arbitrary Java threads. All public entry points are thread safe, and delegate
      the work to the |  | 
|   38 // camera thread. The internal *OnCameraThread() methods must check |camera| for
      null to check if |  | 
|   39 // the camera has been stopped. |  | 
|   40 // TODO(magjed): This class name is now confusing - rename to Camera1VideoCaptur
     er. |  | 
|   41 @SuppressWarnings("deprecation") |  | 
|   42 public class VideoCapturerAndroid implements |  | 
|   43     CameraVideoCapturer, |  | 
|   44     android.hardware.Camera.PreviewCallback, |  | 
|   45     SurfaceTextureHelper.OnTextureFrameAvailableListener { |  | 
|   46   private final static String TAG = "VideoCapturerAndroid"; |  | 
|   47   private static final int CAMERA_STOP_TIMEOUT_MS = 7000; |  | 
|   48  |  | 
|   49   private android.hardware.Camera camera;  // Only non-null while capturing. |  | 
|   50   private final Object handlerLock = new Object(); |  | 
|   51   // |cameraThreadHandler| must be synchronized on |handlerLock| when not on the
      camera thread, |  | 
|   52   // or when modifying the reference. Use maybePostOnCameraThread() instead of p
     osting directly to |  | 
|   53   // the handler - this way all callbacks with a specifed token can be removed a
     t once. |  | 
|   54   private Handler cameraThreadHandler; |  | 
|   55   private Context applicationContext; |  | 
|   56   // Synchronization lock for |id|. |  | 
|   57   private final Object cameraIdLock = new Object(); |  | 
|   58   private int id; |  | 
|   59   private android.hardware.Camera.CameraInfo info; |  | 
|   60   private CameraStatistics cameraStatistics; |  | 
|   61   // Remember the requested format in case we want to switch cameras. |  | 
|   62   private int requestedWidth; |  | 
|   63   private int requestedHeight; |  | 
|   64   private int requestedFramerate; |  | 
|   65   // The capture format will be the closest supported format to the requested fo
     rmat. |  | 
|   66   private CaptureFormat captureFormat; |  | 
|   67   private final Object pendingCameraSwitchLock = new Object(); |  | 
|   68   private volatile boolean pendingCameraSwitch; |  | 
|   69   private CapturerObserver frameObserver = null; |  | 
|   70   private final CameraEventsHandler eventsHandler; |  | 
|   71   private boolean firstFrameReported; |  | 
|   72   // Arbitrary queue depth.  Higher number means more memory allocated & held, |  | 
|   73   // lower number means more sensitivity to processing time in the client (and |  | 
|   74   // potentially stalling the capturer if it runs out of buffers to write to). |  | 
|   75   private static final int NUMBER_OF_CAPTURE_BUFFERS = 3; |  | 
|   76   private final Set<byte[]> queuedBuffers = new HashSet<byte[]>(); |  | 
|   77   private final boolean isCapturingToTexture; |  | 
|   78   private SurfaceTextureHelper surfaceHelper; |  | 
|   79   private final static int MAX_OPEN_CAMERA_ATTEMPTS = 3; |  | 
|   80   private final static int OPEN_CAMERA_DELAY_MS = 500; |  | 
|   81   private int openCameraAttempts; |  | 
|   82  |  | 
|   83   // Camera error callback. |  | 
|   84   private final android.hardware.Camera.ErrorCallback cameraErrorCallback = |  | 
|   85       new android.hardware.Camera.ErrorCallback() { |  | 
|   86     @Override |  | 
|   87     public void onError(int error, android.hardware.Camera camera) { |  | 
|   88       String errorMessage; |  | 
|   89       if (error == android.hardware.Camera.CAMERA_ERROR_SERVER_DIED) { |  | 
|   90         errorMessage = "Camera server died!"; |  | 
|   91       } else { |  | 
|   92         errorMessage = "Camera error: " + error; |  | 
|   93       } |  | 
|   94       Logging.e(TAG, errorMessage); |  | 
|   95       if (eventsHandler != null) { |  | 
|   96         eventsHandler.onCameraError(errorMessage); |  | 
|   97       } |  | 
|   98     } |  | 
|   99   }; |  | 
|  100  |  | 
|  101   public static VideoCapturerAndroid create(String name, |  | 
|  102       CameraEventsHandler eventsHandler) { |  | 
|  103     return VideoCapturerAndroid.create(name, eventsHandler, false /* captureToTe
     xture */); |  | 
|  104   } |  | 
|  105  |  | 
|  106   // Use ctor directly instead. |  | 
|  107   @Deprecated |  | 
|  108   public static VideoCapturerAndroid create(String name, |  | 
|  109       CameraEventsHandler eventsHandler, boolean captureToTexture) { |  | 
|  110     try { |  | 
|  111       return new VideoCapturerAndroid(name, eventsHandler, captureToTexture); |  | 
|  112     } catch (RuntimeException e) { |  | 
|  113       Logging.e(TAG, "Couldn't create camera.", e); |  | 
|  114       return null; |  | 
|  115     } |  | 
|  116   } |  | 
|  117  |  | 
|  118   public void printStackTrace() { |  | 
|  119     Thread cameraThread = null; |  | 
|  120     synchronized (handlerLock) { |  | 
|  121       if (cameraThreadHandler != null) { |  | 
|  122         cameraThread = cameraThreadHandler.getLooper().getThread(); |  | 
|  123       } |  | 
|  124     } |  | 
|  125     if (cameraThread != null) { |  | 
|  126       StackTraceElement[] cameraStackTraces = cameraThread.getStackTrace(); |  | 
|  127       if (cameraStackTraces.length > 0) { |  | 
|  128         Logging.d(TAG, "VideoCapturerAndroid stacks trace:"); |  | 
|  129         for (StackTraceElement stackTrace : cameraStackTraces) { |  | 
|  130           Logging.d(TAG, stackTrace.toString()); |  | 
|  131         } |  | 
|  132       } |  | 
|  133     } |  | 
|  134   } |  | 
|  135  |  | 
|  136   // Switch camera to the next valid camera id. This can only be called while |  | 
|  137   // the camera is running. |  | 
|  138   @Override |  | 
|  139   public void switchCamera(final CameraSwitchHandler switchEventsHandler) { |  | 
|  140     if (android.hardware.Camera.getNumberOfCameras() < 2) { |  | 
|  141       if (switchEventsHandler != null) { |  | 
|  142         switchEventsHandler.onCameraSwitchError("No camera to switch to."); |  | 
|  143       } |  | 
|  144       return; |  | 
|  145     } |  | 
|  146     synchronized (pendingCameraSwitchLock) { |  | 
|  147       if (pendingCameraSwitch) { |  | 
|  148         // Do not handle multiple camera switch request to avoid blocking |  | 
|  149         // camera thread by handling too many switch request from a queue. |  | 
|  150         Logging.w(TAG, "Ignoring camera switch request."); |  | 
|  151         if (switchEventsHandler != null) { |  | 
|  152           switchEventsHandler.onCameraSwitchError("Pending camera switch already
      in progress."); |  | 
|  153         } |  | 
|  154         return; |  | 
|  155       } |  | 
|  156       pendingCameraSwitch = true; |  | 
|  157     } |  | 
|  158     final boolean didPost = maybePostOnCameraThread(new Runnable() { |  | 
|  159       @Override |  | 
|  160       public void run() { |  | 
|  161         switchCameraOnCameraThread(); |  | 
|  162         synchronized (pendingCameraSwitchLock) { |  | 
|  163           pendingCameraSwitch = false; |  | 
|  164         } |  | 
|  165         if (switchEventsHandler != null) { |  | 
|  166           switchEventsHandler.onCameraSwitchDone( |  | 
|  167               info.facing == android.hardware.Camera.CameraInfo.CAMERA_FACING_FR
     ONT); |  | 
|  168         } |  | 
|  169       } |  | 
|  170     }); |  | 
|  171     if (!didPost && switchEventsHandler != null) { |  | 
|  172       switchEventsHandler.onCameraSwitchError("Camera is stopped."); |  | 
|  173     } |  | 
|  174   } |  | 
|  175  |  | 
|  176   // Requests a new output format from the video capturer. Captured frames |  | 
|  177   // by the camera will be scaled/or dropped by the video capturer. |  | 
|  178   // It does not matter if width and height are flipped. I.E, |width| = 640, |he
     ight| = 480 produce |  | 
|  179   // the same result as |width| = 480, |height| = 640. |  | 
|  180   // TODO(magjed/perkj): Document what this function does. Change name? |  | 
|  181   @Override |  | 
|  182   public void onOutputFormatRequest(final int width, final int height, final int
      framerate) { |  | 
|  183     maybePostOnCameraThread(new Runnable() { |  | 
|  184       @Override public void run() { |  | 
|  185         onOutputFormatRequestOnCameraThread(width, height, framerate); |  | 
|  186       } |  | 
|  187     }); |  | 
|  188   } |  | 
|  189  |  | 
|  190   // Reconfigure the camera to capture in a new format. This should only be call
     ed while the camera |  | 
|  191   // is running. |  | 
|  192   @Override |  | 
|  193   public void changeCaptureFormat(final int width, final int height, final int f
     ramerate) { |  | 
|  194     maybePostOnCameraThread(new Runnable() { |  | 
|  195       @Override public void run() { |  | 
|  196         startPreviewOnCameraThread(width, height, framerate); |  | 
|  197       } |  | 
|  198     }); |  | 
|  199   } |  | 
|  200  |  | 
|  201   // Helper function to retrieve the current camera id synchronously. Note that 
     the camera id might |  | 
|  202   // change at any point by switchCamera() calls. |  | 
|  203   private int getCurrentCameraId() { |  | 
|  204     synchronized (cameraIdLock) { |  | 
|  205       return id; |  | 
|  206     } |  | 
|  207   } |  | 
|  208  |  | 
|  209   @Override |  | 
|  210   public List<CaptureFormat> getSupportedFormats() { |  | 
|  211     return Camera1Enumerator.getSupportedFormats(getCurrentCameraId()); |  | 
|  212   } |  | 
|  213  |  | 
|  214   // Returns true if this VideoCapturer is setup to capture video frames to a Su
     rfaceTexture. |  | 
|  215   public boolean isCapturingToTexture() { |  | 
|  216     return isCapturingToTexture; |  | 
|  217   } |  | 
|  218  |  | 
|  219   public VideoCapturerAndroid(String cameraName, CameraEventsHandler eventsHandl
     er, |  | 
|  220       boolean captureToTexture) { |  | 
|  221     if (android.hardware.Camera.getNumberOfCameras() == 0) { |  | 
|  222       throw new RuntimeException("No cameras available"); |  | 
|  223     } |  | 
|  224     if (cameraName == null || cameraName.equals("")) { |  | 
|  225       this.id = 0; |  | 
|  226     } else { |  | 
|  227       this.id = Camera1Enumerator.getCameraIndex(cameraName); |  | 
|  228     } |  | 
|  229     this.eventsHandler = eventsHandler; |  | 
|  230     isCapturingToTexture = captureToTexture; |  | 
|  231     Logging.d(TAG, "VideoCapturerAndroid isCapturingToTexture : " + isCapturingT
     oTexture); |  | 
|  232   } |  | 
|  233  |  | 
|  234   private void checkIsOnCameraThread() { |  | 
|  235     synchronized (handlerLock) { |  | 
|  236       if (cameraThreadHandler == null) { |  | 
|  237         Logging.e(TAG, "Camera is stopped - can't check thread."); |  | 
|  238       } else if (Thread.currentThread() != cameraThreadHandler.getLooper().getTh
     read()) { |  | 
|  239         throw new IllegalStateException("Wrong thread"); |  | 
|  240       } |  | 
|  241     } |  | 
|  242   } |  | 
|  243  |  | 
|  244   private boolean maybePostOnCameraThread(Runnable runnable) { |  | 
|  245     return maybePostDelayedOnCameraThread(0 /* delayMs */, runnable); |  | 
|  246   } |  | 
|  247  |  | 
|  248   private boolean maybePostDelayedOnCameraThread(int delayMs, Runnable runnable)
      { |  | 
|  249     synchronized (handlerLock) { |  | 
|  250       return cameraThreadHandler != null |  | 
|  251           && cameraThreadHandler.postAtTime( |  | 
|  252               runnable, this /* token */, SystemClock.uptimeMillis() + delayMs); |  | 
|  253     } |  | 
|  254   } |  | 
|  255  |  | 
|  256   @Override |  | 
|  257   public void dispose() { |  | 
|  258     Logging.d(TAG, "dispose"); |  | 
|  259   } |  | 
|  260  |  | 
|  261   // Note that this actually opens the camera, and Camera callbacks run on the |  | 
|  262   // thread that calls open(), so this is done on the CameraThread. |  | 
|  263   @Override |  | 
|  264   public void startCapture( |  | 
|  265       final int width, final int height, final int framerate, |  | 
|  266       final SurfaceTextureHelper surfaceTextureHelper, final Context application
     Context, |  | 
|  267       final CapturerObserver frameObserver) { |  | 
|  268     Logging.d(TAG, "startCapture requested: " + width + "x" + height + "@" + fra
     merate); |  | 
|  269     if (surfaceTextureHelper == null) { |  | 
|  270       frameObserver.onCapturerStarted(false /* success */); |  | 
|  271       if (eventsHandler != null) { |  | 
|  272         eventsHandler.onCameraError("No SurfaceTexture created."); |  | 
|  273       } |  | 
|  274       return; |  | 
|  275     } |  | 
|  276     if (applicationContext == null) { |  | 
|  277       throw new IllegalArgumentException("applicationContext not set."); |  | 
|  278     } |  | 
|  279     if (frameObserver == null) { |  | 
|  280       throw new IllegalArgumentException("frameObserver not set."); |  | 
|  281     } |  | 
|  282     synchronized (handlerLock) { |  | 
|  283       if (this.cameraThreadHandler != null) { |  | 
|  284         throw new RuntimeException("Camera has already been started."); |  | 
|  285       } |  | 
|  286       this.cameraThreadHandler = surfaceTextureHelper.getHandler(); |  | 
|  287       this.surfaceHelper = surfaceTextureHelper; |  | 
|  288       final boolean didPost = maybePostOnCameraThread(new Runnable() { |  | 
|  289         @Override |  | 
|  290         public void run() { |  | 
|  291           openCameraAttempts = 0; |  | 
|  292           startCaptureOnCameraThread(width, height, framerate, frameObserver, |  | 
|  293               applicationContext); |  | 
|  294         } |  | 
|  295       }); |  | 
|  296       if (!didPost) { |  | 
|  297         frameObserver.onCapturerStarted(false); |  | 
|  298         if (eventsHandler != null) { |  | 
|  299           eventsHandler.onCameraError("Could not post task to camera thread."); |  | 
|  300         } |  | 
|  301       } |  | 
|  302     } |  | 
|  303   } |  | 
|  304  |  | 
|  305   private void startCaptureOnCameraThread( |  | 
|  306       final int width, final int height, final int framerate, final CapturerObse
     rver frameObserver, |  | 
|  307       final Context applicationContext) { |  | 
|  308     synchronized (handlerLock) { |  | 
|  309       if (cameraThreadHandler == null) { |  | 
|  310         Logging.e(TAG, "startCaptureOnCameraThread: Camera is stopped"); |  | 
|  311         return; |  | 
|  312       } else { |  | 
|  313         checkIsOnCameraThread(); |  | 
|  314       } |  | 
|  315     } |  | 
|  316     if (camera != null) { |  | 
|  317       Logging.e(TAG, "startCaptureOnCameraThread: Camera has already been starte
     d."); |  | 
|  318       return; |  | 
|  319     } |  | 
|  320     this.applicationContext = applicationContext; |  | 
|  321     this.frameObserver = frameObserver; |  | 
|  322     this.firstFrameReported = false; |  | 
|  323  |  | 
|  324     try { |  | 
|  325       try { |  | 
|  326         synchronized (cameraIdLock) { |  | 
|  327           Logging.d(TAG, "Opening camera " + id); |  | 
|  328           if (eventsHandler != null) { |  | 
|  329             eventsHandler.onCameraOpening(id); |  | 
|  330           } |  | 
|  331           camera = android.hardware.Camera.open(id); |  | 
|  332           info = new android.hardware.Camera.CameraInfo(); |  | 
|  333           android.hardware.Camera.getCameraInfo(id, info); |  | 
|  334         } |  | 
|  335       } catch (RuntimeException e) { |  | 
|  336         openCameraAttempts++; |  | 
|  337         if (openCameraAttempts < MAX_OPEN_CAMERA_ATTEMPTS) { |  | 
|  338           Logging.e(TAG, "Camera.open failed, retrying", e); |  | 
|  339           maybePostDelayedOnCameraThread(OPEN_CAMERA_DELAY_MS, new Runnable() { |  | 
|  340             @Override public void run() { |  | 
|  341               startCaptureOnCameraThread(width, height, framerate, frameObserver
     , |  | 
|  342                   applicationContext); |  | 
|  343             } |  | 
|  344           }); |  | 
|  345           return; |  | 
|  346         } |  | 
|  347         throw e; |  | 
|  348       } |  | 
|  349  |  | 
|  350       camera.setPreviewTexture(surfaceHelper.getSurfaceTexture()); |  | 
|  351  |  | 
|  352       Logging.d(TAG, "Camera orientation: " + info.orientation + |  | 
|  353           " .Device orientation: " + getDeviceOrientation()); |  | 
|  354       camera.setErrorCallback(cameraErrorCallback); |  | 
|  355       startPreviewOnCameraThread(width, height, framerate); |  | 
|  356       frameObserver.onCapturerStarted(true); |  | 
|  357       if (isCapturingToTexture) { |  | 
|  358         surfaceHelper.startListening(this); |  | 
|  359       } |  | 
|  360  |  | 
|  361       // Start camera observer. |  | 
|  362       cameraStatistics = new CameraStatistics(surfaceHelper, eventsHandler); |  | 
|  363     } catch (IOException|RuntimeException e) { |  | 
|  364       Logging.e(TAG, "startCapture failed", e); |  | 
|  365       // Make sure the camera is released. |  | 
|  366       stopCaptureOnCameraThread(true /* stopHandler */); |  | 
|  367       frameObserver.onCapturerStarted(false); |  | 
|  368       if (eventsHandler != null) { |  | 
|  369         eventsHandler.onCameraError("Camera can not be started."); |  | 
|  370       } |  | 
|  371      } |  | 
|  372   } |  | 
|  373  |  | 
|  374   // (Re)start preview with the closest supported format to |width| x |height| @
      |framerate|. |  | 
|  375   private void startPreviewOnCameraThread(int width, int height, int framerate) 
     { |  | 
|  376     synchronized (handlerLock) { |  | 
|  377       if (cameraThreadHandler == null || camera == null) { |  | 
|  378         Logging.e(TAG, "startPreviewOnCameraThread: Camera is stopped"); |  | 
|  379         return; |  | 
|  380       } else { |  | 
|  381         checkIsOnCameraThread(); |  | 
|  382       } |  | 
|  383     } |  | 
|  384     Logging.d( |  | 
|  385         TAG, "startPreviewOnCameraThread requested: " + width + "x" + height + "
     @" + framerate); |  | 
|  386  |  | 
|  387     requestedWidth = width; |  | 
|  388     requestedHeight = height; |  | 
|  389     requestedFramerate = framerate; |  | 
|  390  |  | 
|  391     // Find closest supported format for |width| x |height| @ |framerate|. |  | 
|  392     final android.hardware.Camera.Parameters parameters = camera.getParameters()
     ; |  | 
|  393     final List<CaptureFormat.FramerateRange> supportedFramerates = |  | 
|  394         Camera1Enumerator.convertFramerates(parameters.getSupportedPreviewFpsRan
     ge()); |  | 
|  395     Logging.d(TAG, "Available fps ranges: " + supportedFramerates); |  | 
|  396  |  | 
|  397     final CaptureFormat.FramerateRange fpsRange = |  | 
|  398         CameraEnumerationAndroid.getClosestSupportedFramerateRange(supportedFram
     erates, framerate); |  | 
|  399  |  | 
|  400     final Size previewSize = CameraEnumerationAndroid.getClosestSupportedSize( |  | 
|  401         Camera1Enumerator.convertSizes(parameters.getSupportedPreviewSizes()), w
     idth, height); |  | 
|  402  |  | 
|  403     final CaptureFormat captureFormat = |  | 
|  404         new CaptureFormat(previewSize.width, previewSize.height, fpsRange); |  | 
|  405  |  | 
|  406     // Check if we are already using this capture format, then we don't need to 
     do anything. |  | 
|  407     if (captureFormat.equals(this.captureFormat)) { |  | 
|  408       return; |  | 
|  409     } |  | 
|  410  |  | 
|  411     // Update camera parameters. |  | 
|  412     Logging.d(TAG, "isVideoStabilizationSupported: " + |  | 
|  413         parameters.isVideoStabilizationSupported()); |  | 
|  414     if (parameters.isVideoStabilizationSupported()) { |  | 
|  415       parameters.setVideoStabilization(true); |  | 
|  416     } |  | 
|  417     // Note: setRecordingHint(true) actually decrease frame rate on N5. |  | 
|  418     // parameters.setRecordingHint(true); |  | 
|  419     if (captureFormat.framerate.max > 0) { |  | 
|  420       parameters.setPreviewFpsRange(captureFormat.framerate.min, captureFormat.f
     ramerate.max); |  | 
|  421     } |  | 
|  422     parameters.setPreviewSize(previewSize.width, previewSize.height); |  | 
|  423  |  | 
|  424     if (!isCapturingToTexture) { |  | 
|  425       parameters.setPreviewFormat(captureFormat.imageFormat); |  | 
|  426     } |  | 
|  427     // Picture size is for taking pictures and not for preview/video, but we nee
     d to set it anyway |  | 
|  428     // as a workaround for an aspect ratio problem on Nexus 7. |  | 
|  429     final Size pictureSize = CameraEnumerationAndroid.getClosestSupportedSize( |  | 
|  430         Camera1Enumerator.convertSizes(parameters.getSupportedPictureSizes()), w
     idth, height); |  | 
|  431     parameters.setPictureSize(pictureSize.width, pictureSize.height); |  | 
|  432  |  | 
|  433     // Temporarily stop preview if it's already running. |  | 
|  434     if (this.captureFormat != null) { |  | 
|  435       camera.stopPreview(); |  | 
|  436       // Calling |setPreviewCallbackWithBuffer| with null should clear the inter
     nal camera buffer |  | 
|  437       // queue, but sometimes we receive a frame with the old resolution after t
     his call anyway. |  | 
|  438       camera.setPreviewCallbackWithBuffer(null); |  | 
|  439     } |  | 
|  440  |  | 
|  441     // (Re)start preview. |  | 
|  442     Logging.d(TAG, "Start capturing: " + captureFormat); |  | 
|  443     this.captureFormat = captureFormat; |  | 
|  444  |  | 
|  445     List<String> focusModes = parameters.getSupportedFocusModes(); |  | 
|  446     if (focusModes.contains(android.hardware.Camera.Parameters.FOCUS_MODE_CONTIN
     UOUS_VIDEO)) { |  | 
|  447       parameters.setFocusMode(android.hardware.Camera.Parameters.FOCUS_MODE_CONT
     INUOUS_VIDEO); |  | 
|  448     } |  | 
|  449  |  | 
|  450     camera.setParameters(parameters); |  | 
|  451     // Calculate orientation manually and send it as CVO instead. |  | 
|  452     camera.setDisplayOrientation(0 /* degrees */); |  | 
|  453     if (!isCapturingToTexture) { |  | 
|  454       queuedBuffers.clear(); |  | 
|  455       final int frameSize = captureFormat.frameSize(); |  | 
|  456       for (int i = 0; i < NUMBER_OF_CAPTURE_BUFFERS; ++i) { |  | 
|  457         final ByteBuffer buffer = ByteBuffer.allocateDirect(frameSize); |  | 
|  458         queuedBuffers.add(buffer.array()); |  | 
|  459         camera.addCallbackBuffer(buffer.array()); |  | 
|  460       } |  | 
|  461       camera.setPreviewCallbackWithBuffer(this); |  | 
|  462     } |  | 
|  463     camera.startPreview(); |  | 
|  464   } |  | 
|  465  |  | 
|  466   // Blocks until camera is known to be stopped. |  | 
|  467   @Override |  | 
|  468   public void stopCapture() throws InterruptedException { |  | 
|  469     Logging.d(TAG, "stopCapture"); |  | 
|  470     final CountDownLatch barrier = new CountDownLatch(1); |  | 
|  471     final boolean didPost = maybePostOnCameraThread(new Runnable() { |  | 
|  472       @Override public void run() { |  | 
|  473         stopCaptureOnCameraThread(true /* stopHandler */); |  | 
|  474         barrier.countDown(); |  | 
|  475       } |  | 
|  476     }); |  | 
|  477     if (!didPost) { |  | 
|  478       Logging.e(TAG, "Calling stopCapture() for already stopped camera."); |  | 
|  479       return; |  | 
|  480     } |  | 
|  481     if (!barrier.await(CAMERA_STOP_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { |  | 
|  482       Logging.e(TAG, "Camera stop timeout"); |  | 
|  483       printStackTrace(); |  | 
|  484       if (eventsHandler != null) { |  | 
|  485         eventsHandler.onCameraError("Camera stop timeout"); |  | 
|  486       } |  | 
|  487     } |  | 
|  488     Logging.d(TAG, "stopCapture done"); |  | 
|  489   } |  | 
|  490  |  | 
|  491   private void stopCaptureOnCameraThread(boolean stopHandler) { |  | 
|  492     synchronized (handlerLock) { |  | 
|  493       if (cameraThreadHandler == null) { |  | 
|  494         Logging.e(TAG, "stopCaptureOnCameraThread: Camera is stopped"); |  | 
|  495       } else { |  | 
|  496         checkIsOnCameraThread(); |  | 
|  497       } |  | 
|  498     } |  | 
|  499     Logging.d(TAG, "stopCaptureOnCameraThread"); |  | 
|  500     // Note that the camera might still not be started here if startCaptureOnCam
     eraThread failed |  | 
|  501     // and we posted a retry. |  | 
|  502  |  | 
|  503     // Make sure onTextureFrameAvailable() is not called anymore. |  | 
|  504     if (surfaceHelper != null) { |  | 
|  505       surfaceHelper.stopListening(); |  | 
|  506     } |  | 
|  507     if (stopHandler) { |  | 
|  508       synchronized (handlerLock) { |  | 
|  509         // Clear the cameraThreadHandler first, in case stopPreview or |  | 
|  510         // other driver code deadlocks. Deadlock in |  | 
|  511         // android.hardware.Camera._stopPreview(Native Method) has |  | 
|  512         // been observed on Nexus 5 (hammerhead), OS version LMY48I. |  | 
|  513         // The camera might post another one or two preview frames |  | 
|  514         // before stopped, so we have to check for a null |  | 
|  515         // cameraThreadHandler in our handler. Remove all pending |  | 
|  516         // Runnables posted from |this|. |  | 
|  517         if (cameraThreadHandler != null) { |  | 
|  518           cameraThreadHandler.removeCallbacksAndMessages(this /* token */); |  | 
|  519           cameraThreadHandler = null; |  | 
|  520         } |  | 
|  521         surfaceHelper = null; |  | 
|  522       } |  | 
|  523     } |  | 
|  524     if (cameraStatistics != null) { |  | 
|  525       cameraStatistics.release(); |  | 
|  526       cameraStatistics = null; |  | 
|  527     } |  | 
|  528     Logging.d(TAG, "Stop preview."); |  | 
|  529     if (camera != null) { |  | 
|  530       camera.stopPreview(); |  | 
|  531       camera.setPreviewCallbackWithBuffer(null); |  | 
|  532     } |  | 
|  533     queuedBuffers.clear(); |  | 
|  534     captureFormat = null; |  | 
|  535  |  | 
|  536     Logging.d(TAG, "Release camera."); |  | 
|  537     if (camera != null) { |  | 
|  538       camera.release(); |  | 
|  539       camera = null; |  | 
|  540     } |  | 
|  541     if (eventsHandler != null) { |  | 
|  542       eventsHandler.onCameraClosed(); |  | 
|  543     } |  | 
|  544     Logging.d(TAG, "stopCaptureOnCameraThread done"); |  | 
|  545   } |  | 
|  546  |  | 
|  547   private void switchCameraOnCameraThread() { |  | 
|  548     synchronized (handlerLock) { |  | 
|  549       if (cameraThreadHandler == null) { |  | 
|  550         Logging.e(TAG, "switchCameraOnCameraThread: Camera is stopped"); |  | 
|  551         return; |  | 
|  552       } else { |  | 
|  553         checkIsOnCameraThread(); |  | 
|  554       } |  | 
|  555     } |  | 
|  556     Logging.d(TAG, "switchCameraOnCameraThread"); |  | 
|  557     stopCaptureOnCameraThread(false /* stopHandler */); |  | 
|  558     synchronized (cameraIdLock) { |  | 
|  559       id = (id + 1) % android.hardware.Camera.getNumberOfCameras(); |  | 
|  560     } |  | 
|  561     startCaptureOnCameraThread(requestedWidth, requestedHeight, requestedFramera
     te, frameObserver, |  | 
|  562         applicationContext); |  | 
|  563     Logging.d(TAG, "switchCameraOnCameraThread done"); |  | 
|  564   } |  | 
|  565  |  | 
|  566   private void onOutputFormatRequestOnCameraThread(int width, int height, int fr
     amerate) { |  | 
|  567     synchronized (handlerLock) { |  | 
|  568       if (cameraThreadHandler == null || camera == null) { |  | 
|  569         Logging.e(TAG, "onOutputFormatRequestOnCameraThread: Camera is stopped")
     ; |  | 
|  570         return; |  | 
|  571       } else { |  | 
|  572         checkIsOnCameraThread(); |  | 
|  573       } |  | 
|  574     } |  | 
|  575     Logging.d(TAG, "onOutputFormatRequestOnCameraThread: " + width + "x" + heigh
     t + |  | 
|  576         "@" + framerate); |  | 
|  577     frameObserver.onOutputFormatRequest(width, height, framerate); |  | 
|  578   } |  | 
|  579  |  | 
|  580   private int getDeviceOrientation() { |  | 
|  581     int orientation = 0; |  | 
|  582  |  | 
|  583     WindowManager wm = (WindowManager) applicationContext.getSystemService( |  | 
|  584         Context.WINDOW_SERVICE); |  | 
|  585     switch(wm.getDefaultDisplay().getRotation()) { |  | 
|  586       case Surface.ROTATION_90: |  | 
|  587         orientation = 90; |  | 
|  588         break; |  | 
|  589       case Surface.ROTATION_180: |  | 
|  590         orientation = 180; |  | 
|  591         break; |  | 
|  592       case Surface.ROTATION_270: |  | 
|  593         orientation = 270; |  | 
|  594         break; |  | 
|  595       case Surface.ROTATION_0: |  | 
|  596       default: |  | 
|  597         orientation = 0; |  | 
|  598         break; |  | 
|  599     } |  | 
|  600     return orientation; |  | 
|  601   } |  | 
|  602  |  | 
|  603   private int getFrameOrientation() { |  | 
|  604     int rotation = getDeviceOrientation(); |  | 
|  605     if (info.facing == android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK) { |  | 
|  606       rotation = 360 - rotation; |  | 
|  607     } |  | 
|  608     return (info.orientation + rotation) % 360; |  | 
|  609   } |  | 
|  610  |  | 
|  611   // Called on cameraThread so must not "synchronized". |  | 
|  612   @Override |  | 
|  613   public void onPreviewFrame(byte[] data, android.hardware.Camera callbackCamera
     ) { |  | 
|  614     synchronized (handlerLock) { |  | 
|  615       if (cameraThreadHandler == null) { |  | 
|  616         Logging.e(TAG, "onPreviewFrame: Camera is stopped"); |  | 
|  617         return; |  | 
|  618       } else { |  | 
|  619         checkIsOnCameraThread(); |  | 
|  620       } |  | 
|  621     } |  | 
|  622     if (!queuedBuffers.contains(data)) { |  | 
|  623       // |data| is an old invalid buffer. |  | 
|  624       return; |  | 
|  625     } |  | 
|  626     if (camera != callbackCamera) { |  | 
|  627       throw new RuntimeException("Unexpected camera in callback!"); |  | 
|  628     } |  | 
|  629  |  | 
|  630     final long captureTimeNs = |  | 
|  631         TimeUnit.MILLISECONDS.toNanos(SystemClock.elapsedRealtime()); |  | 
|  632  |  | 
|  633     if (eventsHandler != null && !firstFrameReported) { |  | 
|  634       eventsHandler.onFirstFrameAvailable(); |  | 
|  635       firstFrameReported = true; |  | 
|  636     } |  | 
|  637  |  | 
|  638     cameraStatistics.addFrame(); |  | 
|  639     frameObserver.onByteBufferFrameCaptured(data, captureFormat.width, captureFo
     rmat.height, |  | 
|  640         getFrameOrientation(), captureTimeNs); |  | 
|  641     camera.addCallbackBuffer(data); |  | 
|  642   } |  | 
|  643  |  | 
|  644   @Override |  | 
|  645   public void onTextureFrameAvailable( |  | 
|  646       int oesTextureId, float[] transformMatrix, long timestampNs) { |  | 
|  647     synchronized (handlerLock) { |  | 
|  648       if (cameraThreadHandler == null) { |  | 
|  649         Logging.e(TAG, "onTextureFrameAvailable: Camera is stopped"); |  | 
|  650         surfaceHelper.returnTextureFrame(); |  | 
|  651         return; |  | 
|  652       } else { |  | 
|  653         checkIsOnCameraThread(); |  | 
|  654       } |  | 
|  655     } |  | 
|  656     if (eventsHandler != null && !firstFrameReported) { |  | 
|  657       eventsHandler.onFirstFrameAvailable(); |  | 
|  658       firstFrameReported = true; |  | 
|  659     } |  | 
|  660  |  | 
|  661     int rotation = getFrameOrientation(); |  | 
|  662     if (info.facing == android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT) { |  | 
|  663       // Undo the mirror that the OS "helps" us with. |  | 
|  664       // http://developer.android.com/reference/android/hardware/Camera.html#set
     DisplayOrientation(int) |  | 
|  665       transformMatrix = |  | 
|  666           RendererCommon.multiplyMatrices(transformMatrix, RendererCommon.horizo
     ntalFlipMatrix()); |  | 
|  667     } |  | 
|  668     cameraStatistics.addFrame(); |  | 
|  669     frameObserver.onTextureFrameCaptured(captureFormat.width, captureFormat.heig
     ht, oesTextureId, |  | 
|  670         transformMatrix, rotation, timestampNs); |  | 
|  671   } |  | 
|  672 } |  | 
| OLD | NEW |