1 /*
2 AndroidVideoApi9JniWrapper.java
3 Copyright (C) 2010  Belledonne Communications, Grenoble, France
4 
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
9 
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14 
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18 */
19 package org.linphone.mediastream.video.capture;
20 
21 import java.util.List;
22 
23 import org.linphone.mediastream.Log;
24 
25 import android.annotation.TargetApi;
26 import android.graphics.ImageFormat;
27 import android.hardware.Camera;
28 import android.hardware.Camera.Parameters;
29 import android.hardware.Camera.Size;
30 import android.os.Build;
31 
32 @TargetApi(Build.VERSION_CODES.GINGERBREAD)
33 public class AndroidVideoApi9JniWrapper {
detectCameras(int[] indexes, int[] frontFacing, int[] orientation)34 	static public int detectCameras(int[] indexes, int[] frontFacing, int[] orientation) {
35 		return AndroidVideoApi5JniWrapper.detectCameras(indexes, frontFacing, orientation);
36 	}
37 
38 	/**
39 	 * Return the hw-available available resolution best matching the requested one.
40 	 * Best matching meaning :
41 	 * - try to find the same one
42 	 * - try to find one just a little bigger (ex: CIF when asked QVGA)
43 	 * - as a fallback the nearest smaller one
44 	 * @param cameraId Camera id
45 	 * @param requestedW Requested video size width
46 	 * @param requestedH Requested video size height
47 	 * @return int[width, height] of the chosen resolution, may be null if no
48 	 * resolution can possibly match the requested one
49 	 */
selectNearestResolutionAvailable(int cameraId, int requestedW, int requestedH)50 	static public int[] selectNearestResolutionAvailable(int cameraId, int requestedW, int requestedH) {
51 		Log.d("selectNearestResolutionAvailable: " + cameraId + ", " + requestedW + "x" + requestedH);
52 		return AndroidVideoApi5JniWrapper.selectNearestResolutionAvailableForCamera(cameraId, requestedW, requestedH);
53 	}
54 
startRecording(int cameraId, int width, int height, int fps, int rotation, final long nativePtr)55 	public static Object startRecording(int cameraId, int width, int height, int fps, int rotation, final long nativePtr) {
56 		Log.d("startRecording(" + cameraId + ", " + width + ", " + height + ", " + fps + ", " + rotation + ", " + nativePtr + ")");
57 		try {
58 		Camera camera = Camera.open(cameraId);
59 		Parameters params = camera.getParameters();
60 
61 		params.setPreviewSize(width, height);
62 		int[] chosenFps = findClosestEnclosingFpsRange(fps*1000, params.getSupportedPreviewFpsRange());
63 		params.setPreviewFpsRange(chosenFps[0], chosenFps[1]);
64 		camera.setParameters(params);
65 
66 		int bufferSize = (width * height * ImageFormat.getBitsPerPixel(params.getPreviewFormat())) / 8;
67 		camera.addCallbackBuffer(new byte[bufferSize]);
68 		camera.addCallbackBuffer(new byte[bufferSize]);
69 
70 		camera.setPreviewCallbackWithBuffer(new Camera.PreviewCallback() {
71 			public void onPreviewFrame(byte[] data, Camera camera) {
72 				// forward image data to JNI
73 				if (data == null) {
74 					// It appears that there is a bug in the camera driver that is asking for a buffer size bigger than it should
75 					Parameters params = camera.getParameters();
76 					Size size = params.getPreviewSize();
77 					int bufferSize = (size.width * size.height * ImageFormat.getBitsPerPixel(params.getPreviewFormat())) / 8;
78 					bufferSize += bufferSize / 20;
79 					camera.addCallbackBuffer(new byte[bufferSize]);
80 				} else if (AndroidVideoApi5JniWrapper.isRecording) {
81 					AndroidVideoApi5JniWrapper.putImage(nativePtr, data);
82 					camera.addCallbackBuffer(data);
83 				}
84 			}
85 		});
86 
87 		setCameraDisplayOrientation(rotation, cameraId, camera);
88 		camera.startPreview();
89 		AndroidVideoApi5JniWrapper.isRecording = true;
90 		Log.d("Returning camera object: " + camera);
91 		return camera;
92 		} catch (Exception exc) {
93 			exc.printStackTrace();
94 			return null;
95 		}
96 	}
97 
stopRecording(Object cam)98 	public static void stopRecording(Object cam) {
99 		AndroidVideoApi5JniWrapper.isRecording = false;
100 		AndroidVideoApi8JniWrapper.stopRecording(cam);
101 	}
102 
setPreviewDisplaySurface(Object cam, Object surf)103 	public static void setPreviewDisplaySurface(Object cam, Object surf) {
104 		AndroidVideoApi5JniWrapper.setPreviewDisplaySurface(cam, surf);
105 	}
106 
setCameraDisplayOrientation(int rotationDegrees, int cameraId, Camera camera)107 	private static void setCameraDisplayOrientation(int rotationDegrees, int cameraId, Camera camera) {
108 		android.hardware.Camera.CameraInfo info = new android.hardware.Camera.CameraInfo();
109 		android.hardware.Camera.getCameraInfo(cameraId, info);
110 
111 		int result;
112 		if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
113 			result = (info.orientation + rotationDegrees) % 360;
114 			result = (360 - result) % 360; // compensate the mirror
115 		} else { // back-facing
116 			result = (info.orientation - rotationDegrees + 360) % 360;
117 		}
118 
119 		Log.w("Camera preview orientation: "+ result);
120 		try {
121 			camera.setDisplayOrientation(result);
122 		} catch (Exception exc) {
123 			Log.e("Failed to execute: camera[" + camera + "].setDisplayOrientation(" + result + ")");
124 			exc.printStackTrace();
125 		}
126 	}
127 
findClosestEnclosingFpsRange(int expectedFps, List<int[]> fpsRanges)128 	private static int[] findClosestEnclosingFpsRange(int expectedFps, List<int[]> fpsRanges) {
129 		Log.d("Searching for closest fps range from " + expectedFps);
130 		if (fpsRanges == null || fpsRanges.size() == 0) {
131 			return new int[] { 0, 0 };
132 		}
133 
134 		// init with first element
135 		int[] closestRange = fpsRanges.get(0);
136 		int measure = Math.abs(closestRange[0] - expectedFps)
137 				+ Math.abs(closestRange[1] - expectedFps);
138 		for (int[] curRange : fpsRanges) {
139 			if (curRange[0] > expectedFps || curRange[1] < expectedFps) continue;
140 			int curMeasure = Math.abs(curRange[0] - expectedFps)
141 					+ Math.abs(curRange[1] - expectedFps);
142 			if (curMeasure < measure) {
143 				closestRange=curRange;
144 				measure = curMeasure;
145 				Log.d("a better range has been found: w="+closestRange[0]+",h="+closestRange[1]);
146 			}
147 		}
148 		Log.d("The closest fps range is w="+closestRange[0]+",h="+closestRange[1]);
149 		return closestRange;
150 	}
151 }
152