/* * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ package org.webrtc.videoengine; import android.graphics.ImageFormat; import android.graphics.PixelFormat; import android.graphics.SurfaceTexture; import android.hardware.Camera; import android.hardware.Camera.PreviewCallback; import android.view.SurfaceHolder; import android.view.SurfaceHolder.Callback; import com.csipsimple.utils.Log; import org.webrtc.videoengine.VideoCaptureDeviceInfoAndroid.AndroidVideoCaptureDevice; import org.webrtc.videoengine.camera.CameraUtilsWrapper; import java.io.IOException; import java.util.concurrent.locks.ReentrantLock; public class VideoCaptureAndroid implements PreviewCallback, Callback { private final static String TAG = "WEBRTC-JC"; private Camera camera; private CameraUtilsWrapper cameraUtils; private AndroidVideoCaptureDevice currentDevice = null; public ReentrantLock previewBufferLock = new ReentrantLock(); // This lock takes sync with StartCapture and SurfaceChanged private ReentrantLock captureLock = new ReentrantLock(); private int PIXEL_FORMAT = ImageFormat.NV21; PixelFormat pixelFormat = new PixelFormat(); // True when the C++ layer has ordered the camera to be started. private boolean isCaptureStarted = false; private boolean isCaptureRunning = false; private boolean isSurfaceReady = false; private SurfaceHolder surfaceHolder = null; private final int numCaptureBuffers = 3; private int expectedFrameSize = 0; private int orientation = 0; private int id = 0; // C++ callback context variable. private long context = 0; private SurfaceHolder localPreview = null; private SurfaceTexture dummySurfaceTexture = null; private int mCaptureWidth = -1; private int mCaptureHeight = -1; private int mCaptureFPS = -1; public static void DeleteVideoCaptureAndroid(VideoCaptureAndroid captureAndroid) { Log.d(TAG, "DeleteVideoCaptureAndroid"); if(captureAndroid.camera == null) { return; } captureAndroid.StopCapture(); captureAndroid.camera.release(); captureAndroid.camera = null; captureAndroid.context = 0; } public VideoCaptureAndroid(int in_id, long in_context, Camera in_camera, AndroidVideoCaptureDevice in_device) { id = in_id; context = in_context; camera = in_camera; currentDevice = in_device; cameraUtils = CameraUtilsWrapper.getInstance(); } @SuppressWarnings("deprecation") private int tryStartCapture(int width, int height, int frameRate) { if (camera == null) { Log.e(TAG, "Camera not initialized %d" + id); return -1; } Log.d(TAG, "tryStartCapture: " + width + "x" + height +", frameRate: " + frameRate + ", isCaptureRunning: " + isCaptureRunning + ", isSurfaceReady: " + isSurfaceReady + ", isCaptureStarted: " + isCaptureStarted); if (isCaptureRunning || !isCaptureStarted) { return 0; } CaptureCapabilityAndroid currentCapability = new CaptureCapabilityAndroid(); currentCapability.width = width; currentCapability.height = height; currentCapability.maxFPS = frameRate; PixelFormat.getPixelFormatInfo(PIXEL_FORMAT, pixelFormat); Camera.Parameters parameters = camera.getParameters(); parameters.setPreviewSize(currentCapability.width, currentCapability.height); parameters.setPreviewFormat(PIXEL_FORMAT); parameters.setPreviewFrameRate(currentCapability.maxFPS); camera.setParameters(parameters); int bufSize = width * height * pixelFormat.bitsPerPixel / 8; cameraUtils.setCallback(this, numCaptureBuffers, bufSize, camera); camera.startPreview(); previewBufferLock.lock(); expectedFrameSize = bufSize; isCaptureRunning = true; previewBufferLock.unlock(); isCaptureRunning = true; return 0; } public int StartCapture(int width, int height, int frameRate) { Log.d(TAG, "StartCapture width " + width + " height " + height +" frame rate " + frameRate); // Get the local preview SurfaceHolder from the static render class localPreview = ViERenderer.GetLocalRenderer(); if (localPreview != null) { if (localPreview.getSurface() != null) { surfaceCreated(localPreview); } localPreview.addCallback(this); } else { // No local renderer. Camera won't capture without // setPreview{Texture,Display}, so we create a dummy SurfaceTexture // and hand it over to Camera, but never listen for frame-ready // callbacks, and never call updateTexImage on it. captureLock.lock(); cameraUtils.setDummyTexture(camera); captureLock.unlock(); } captureLock.lock(); isCaptureStarted = true; mCaptureWidth = width; mCaptureHeight = height; mCaptureFPS = frameRate; int res = tryStartCapture(mCaptureWidth, mCaptureHeight, mCaptureFPS); captureLock.unlock(); return res; } public int StopCapture() { Log.d(TAG, "StopCapture"); try { previewBufferLock.lock(); isCaptureRunning = false; previewBufferLock.unlock(); camera.stopPreview(); cameraUtils.unsetCallback(camera); } catch (RuntimeException ex) { Log.e(TAG, "Failed to stop camera"); return -1; } isCaptureStarted = false; return 0; } native void ProvideCameraFrame(byte[] data, int length, long captureObject); public void onPreviewFrame(byte[] data, Camera camera) { previewBufferLock.lock(); // The following line is for debug only // Log.v(TAG, "preview frame length " + data.length + // " context" + context); if (isCaptureRunning) { // If StartCapture has been called but not StopCapture // Call the C++ layer with the captured frame if (data.length == expectedFrameSize) { ProvideCameraFrame(data, expectedFrameSize, context); // Give the video buffer to the camera service again. cameraUtils.addCallbackBuffer(camera, data); } } previewBufferLock.unlock(); } // Sets the rotation of the preview render window. // Does not affect the captured video image. public void SetPreviewRotation(int rotation) { Log.v(TAG, "SetPreviewRotation:" + rotation); if (camera != null) { previewBufferLock.lock(); int width = 0; int height = 0; int framerate = 0; boolean wasCaptureRunning = isCaptureRunning; if (isCaptureRunning) { width = mCaptureWidth; height = mCaptureHeight; framerate = mCaptureFPS; StopCapture(); } int resultRotation = 0; if (currentDevice.frontCameraType == VideoCaptureDeviceInfoAndroid.FrontFacingCameraType.Android23) { // this is a 2.3 or later front facing camera. // SetDisplayOrientation will flip the image horizontally // before doing the rotation. resultRotation=(360-rotation) % 360; // compensate the mirror } else { // Back facing or 2.2 or previous front camera resultRotation=rotation; } cameraUtils.setDisplayOrientation(camera, resultRotation); if (wasCaptureRunning) { StartCapture(width, height, framerate); } previewBufferLock.unlock(); } } public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { Log.d(TAG, "VideoCaptureAndroid::surfaceChanged"); } public void surfaceCreated(SurfaceHolder holder) { Log.d(TAG, "VideoCaptureAndroid::surfaceCreated"); captureLock.lock(); try { camera.setPreviewDisplay(holder); } catch (IOException e) { Log.e(TAG, "Failed to set preview surface!", e); } captureLock.unlock(); } public void surfaceDestroyed(SurfaceHolder holder) { Log.d(TAG, "VideoCaptureAndroid::surfaceDestroyed"); captureLock.lock(); try { if(camera != null) { camera.setPreviewDisplay(null); } } catch (IOException e) { Log.e(TAG, "Failed to clear preview surface!", e); } catch (RuntimeException e) { Log.w(TAG, "Clear preview surface useless", e); } captureLock.unlock(); } }