/* AndroidCameraRecordImpl.java Copyright (C) 2010 Belledonne Communications, Grenoble, France This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package org.linphone.core.video; import android.hardware.Camera; import android.hardware.Camera.AutoFocusCallback; import android.hardware.Camera.ErrorCallback; import android.hardware.Camera.Parameters; import android.hardware.Camera.PreviewCallback; import android.hardware.Camera.Size; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.view.View.OnClickListener; import org.linphone.core.Log; import org.linphone.core.Version; import org.linphone.core.VideoSize; import java.util.Collections; import java.util.List; public abstract class AndroidCameraRecord implements AutoFocusCallback { protected Camera camera; private RecorderParams params; private PreviewCallback storedPreviewCallback; private boolean previewStarted; private List <VideoSize> supportedVideoSizes; private Size currentPreviewSize; public AndroidCameraRecord(RecorderParams parameters) { this.params = parameters; } protected List<Size> getSupportedPreviewSizes(Parameters parameters) { return Collections.emptyList(); } private int[] findClosestEnclosingFpsRange(int expectedFps, List<int[]> fpsRanges) { Log.d("Searching for closest fps range from ", expectedFps); int measure = Integer.MAX_VALUE; int[] closestRange = fpsRanges.get(0); for (int[] curRange : fpsRanges) { if (curRange[0] > expectedFps || curRange[1] < expectedFps) continue; int curMeasure = Math.abs(curRange[0] - expectedFps) + Math.abs(curRange[1] - expectedFps); if (curMeasure < measure) { closestRange=curRange; Log.d("a better range has been found: w=", closestRange[0], ",h=", closestRange[1]); } } Log.d("The closest fps range is w=", closestRange[0], ",h=", closestRange[1]); return closestRange; } public synchronized void startPreview() { // FIXME throws exception? if (previewStarted) { Log.w("Already started"); throw new RuntimeException("Video recorder already started"); // return } if (params.surfaceView.getVisibility() != SurfaceView.VISIBLE) { // Illegal state Log.e("Illegal state: video capture surface view is not visible"); return; } Log.d("Trying to open camera with id ", params.cameraId); if (camera != null) { Log.e("Camera is not null, ?already open? : aborting"); return; } camera = openCamera(params.cameraId); camera.setErrorCallback(new ErrorCallback() { public void onError(int error, Camera camera) { Log.e("Camera error : ", error); } }); Parameters parameters=camera.getParameters(); if (Version.sdkStrictlyBelow(Version.API09_GINGERBREAD_23)) { parameters.set("camera-id",params.cameraId); } if (supportedVideoSizes == null) { supportedVideoSizes = VideoUtil.createList(getSupportedPreviewSizes(parameters)); } if (params.width >= params.height) { parameters.setPreviewSize(params.width, params.height); } else { // invert height and width parameters.setPreviewSize(params.height, params.width); } // should setParameters and get again to have the real one?? currentPreviewSize = parameters.getPreviewSize(); // Frame rate if (Version.sdkStrictlyBelow(Version.API09_GINGERBREAD_23)) { // Select the supported fps just faster than the target rate List<Integer> supportedFrameRates=parameters.getSupportedPreviewFrameRates(); if (supportedFrameRates != null && supportedFrameRates.size() > 0) { Collections.sort(supportedFrameRates); int selectedRate = -1; for (Integer rate : supportedFrameRates) { selectedRate=rate; if (rate >= params.fps) break; } parameters.setPreviewFrameRate(selectedRate); } } else { List<int[]> supportedRanges = parameters.getSupportedPreviewFpsRange(); int[] range=findClosestEnclosingFpsRange((int)(1000*params.fps), supportedRanges); parameters.setPreviewFpsRange(range[0], range[1]); } onSettingCameraParameters(parameters); camera.setParameters(parameters); SurfaceHolder holder = params.surfaceView.getHolder(); try { camera.setPreviewDisplay(holder); } catch (Throwable t) { Log.e(t, "Exception in Video capture setPreviewDisplay()"); } try { camera.startPreview(); } catch (Throwable e) { Log.e("Error, can't start camera preview. Releasing camera!"); camera.release(); camera = null; return; } previewStarted = true; // Activate autofocus if (Parameters.FOCUS_MODE_AUTO.equals(parameters.getFocusMode())) { OnClickListener svClickListener = new OnClickListener() { public void onClick(View v) { Log.i("Auto focus requested"); camera.autoFocus(AndroidCameraRecord.this); } }; params.surfaceView.setOnClickListener(svClickListener); // svClickListener.onClick(null); } else { params.surfaceView.setOnClickListener(null); } // Register callback to get capture buffer lowLevelSetPreviewCallback(camera, storedPreviewCallback); onPreviewStarted(camera); } protected Camera openCamera(int cameraId) { return Camera.open(); } protected void onSettingCameraParameters(Parameters parameters) {} /** * Hook. * @param camera */ public void onPreviewStarted(Camera camera) {} public void storePreviewCallBack(PreviewCallback cb) { this.storedPreviewCallback = cb; if (camera == null) { Log.w("Capture camera not ready, storing preview callback"); return; } lowLevelSetPreviewCallback(camera, cb); } public void stopPreview() { if (!previewStarted) return; lowLevelSetPreviewCallback(camera, null); camera.stopPreview(); camera.release(); camera=null; Log.d("Camera released"); currentPreviewSize = null; previewStarted = false; } public void stopCaptureCallback() { if (camera != null) { lowLevelSetPreviewCallback(camera, null); } } protected abstract void lowLevelSetPreviewCallback(Camera camera, PreviewCallback cb); public static class RecorderParams { public static enum MirrorType {NO, HORIZONTAL, CENTRAL, VERTICAL}; public float fps; public int height; public int width; final long filterDataNativePtr; public int cameraId; public boolean isFrontCamera; public int rotation; public SurfaceView surfaceView; public MirrorType mirror = MirrorType.NO; public int phoneOrientation; public RecorderParams(long ptr) { filterDataNativePtr = ptr; } } public boolean isStarted() { return previewStarted; } public List<VideoSize> getSupportedVideoSizes() { return supportedVideoSizes; } protected int getExpectedBufferLength() { if (currentPreviewSize == null) return -1; return currentPreviewSize.width * currentPreviewSize.height * 3 /2; } public void onAutoFocus(boolean success, Camera camera) { if (success) Log.i("Autofocus success"); else Log.i("Autofocus failure"); } public int getStoredPhoneOrientation() { return params.phoneOrientation; } }