2 * Copyright (C) 2011 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 package android.filterpacks.videosrc;
20 import android.content.Context;
21 import android.filterfw.core.Filter;
22 import android.filterfw.core.FilterContext;
23 import android.filterfw.core.Frame;
24 import android.filterfw.core.FrameFormat;
25 import android.filterfw.core.FrameManager;
26 import android.filterfw.core.GenerateFieldPort;
27 import android.filterfw.core.GenerateFinalPort;
28 import android.filterfw.core.GLFrame;
29 import android.filterfw.core.KeyValueMap;
30 import android.filterfw.core.MutableFrameFormat;
31 import android.filterfw.core.NativeFrame;
32 import android.filterfw.core.Program;
33 import android.filterfw.core.ShaderProgram;
34 import android.filterfw.format.ImageFormat;
35 import android.graphics.SurfaceTexture;
36 import android.hardware.Camera;
37 import android.os.ConditionVariable;
39 import java.io.IOException;
40 import java.util.List;
43 import android.util.Log;
48 public class CameraSource extends Filter {
50 /** User-visible parameters */
52 /** Camera ID to use for input. Defaults to 0. */
53 @GenerateFieldPort(name = "id", hasDefault = true)
54 private int mCameraId = 0;
56 /** Frame width to request from camera. Actual size may not match requested. */
57 @GenerateFieldPort(name = "width", hasDefault = true)
58 private int mWidth = 320;
60 /** Frame height to request from camera. Actual size may not match requested. */
61 @GenerateFieldPort(name = "height", hasDefault = true)
62 private int mHeight = 240;
64 /** Stream framerate to request from camera. Actual frame rate may not match requested. */
65 @GenerateFieldPort(name = "framerate", hasDefault = true)
66 private int mFps = 30;
68 /** Whether the filter should always wait for a new frame from the camera
69 * before providing output. If set to false, the filter will keep
70 * outputting the last frame it received from the camera if multiple process
71 * calls are received before the next update from the Camera. Defaults to true.
73 @GenerateFinalPort(name = "waitForNewFrame", hasDefault = true)
74 private boolean mWaitForNewFrame = true;
76 private Camera mCamera;
77 private GLFrame mCameraFrame;
78 private SurfaceTexture mSurfaceTexture;
79 private ShaderProgram mFrameExtractor;
80 private MutableFrameFormat mOutputFormat;
81 private float[] mCameraTransform;
83 private static final int NEWFRAME_TIMEOUT = 100; //ms
84 private static final int NEWFRAME_TIMEOUT_REPEAT = 10;
86 private boolean mNewFrameAvailable;
88 private Camera.Parameters mCameraParameters;
90 private static final String mFrameShader =
91 "#extension GL_OES_EGL_image_external : require\n" +
92 "precision mediump float;\n" +
93 "uniform mat4 camera_transform;\n" +
94 "uniform samplerExternalOES tex_sampler_0;\n" +
95 "varying vec2 v_texcoord;\n" +
97 " vec2 transformed_texcoord = (camera_transform * vec4(v_texcoord, 0., 1.) ).xy;" +
98 " gl_FragColor = texture2D(tex_sampler_0, transformed_texcoord);\n" +
101 private final boolean mLogVerbose;
102 private static final String TAG = "CameraSource";
104 public CameraSource(String name) {
106 mCameraTransform = new float[16];
108 mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
112 public void setupPorts() {
114 addOutputPort("video", ImageFormat.create(ImageFormat.COLORSPACE_RGBA,
115 FrameFormat.TARGET_GPU));
118 private void createFormats() {
119 mOutputFormat = ImageFormat.create(mWidth, mHeight,
120 ImageFormat.COLORSPACE_RGBA,
121 FrameFormat.TARGET_GPU);
125 public void prepare(FilterContext context) {
126 if (mLogVerbose) Log.v(TAG, "Preparing");
127 // Compile shader TODO: Move to onGLEnvSomething?
128 mFrameExtractor = new ShaderProgram(context, mFrameShader);
129 // SurfaceTexture defines (0,0) to be bottom-left. The filter framework defines
130 // (0,0) as top-left, so do the flip here.
131 mFrameExtractor.setSourceRect(0, 1, 1, -1);
135 public void open(FilterContext context) {
136 if (mLogVerbose) Log.v(TAG, "Opening");
138 mCamera = Camera.open(mCameraId);
141 getCameraParameters();
142 mCamera.setParameters(mCameraParameters);
144 // Create frame formats
147 // Bind it to our camera frame
148 mCameraFrame = (GLFrame)context.getFrameManager().newBoundFrame(mOutputFormat,
149 GLFrame.EXTERNAL_TEXTURE,
151 mSurfaceTexture = new SurfaceTexture(mCameraFrame.getTextureId());
153 mCamera.setPreviewTexture(mSurfaceTexture);
154 } catch (IOException e) {
155 throw new RuntimeException("Could not bind camera surface texture: " +
156 e.getMessage() + "!");
159 // Connect SurfaceTexture to callback
160 mSurfaceTexture.setOnFrameAvailableListener(onCameraFrameAvailableListener);
162 mNewFrameAvailable = false;
163 mCamera.startPreview();
167 public void process(FilterContext context) {
168 if (mLogVerbose) Log.v(TAG, "Processing new frame");
170 if (mWaitForNewFrame) {
172 while (!mNewFrameAvailable) {
173 if (waitCount == NEWFRAME_TIMEOUT_REPEAT) {
174 throw new RuntimeException("Timeout waiting for new frame");
177 this.wait(NEWFRAME_TIMEOUT);
178 } catch (InterruptedException e) {
179 if (mLogVerbose) Log.v(TAG, "Interrupted while waiting for new frame");
182 mNewFrameAvailable = false;
183 if (mLogVerbose) Log.v(TAG, "Got new frame");
186 mSurfaceTexture.updateTexImage();
188 if (mLogVerbose) Log.v(TAG, "Using frame extractor in thread: " + Thread.currentThread());
189 mSurfaceTexture.getTransformMatrix(mCameraTransform);
190 mFrameExtractor.setHostValue("camera_transform", mCameraTransform);
192 Frame output = context.getFrameManager().newFrame(mOutputFormat);
193 mFrameExtractor.process(mCameraFrame, output);
195 long timestamp = mSurfaceTexture.getTimestamp();
196 if (mLogVerbose) Log.v(TAG, "Timestamp: " + (timestamp / 1000000000.0) + " s");
197 output.setTimestamp(timestamp);
199 pushOutput("video", output);
201 // Release pushed frame
204 if (mLogVerbose) Log.v(TAG, "Done processing new frame");
208 public void close(FilterContext context) {
209 if (mLogVerbose) Log.v(TAG, "Closing");
213 mSurfaceTexture = null;
217 public void tearDown(FilterContext context) {
218 if (mCameraFrame != null) {
219 mCameraFrame.release();
224 public void fieldPortValueUpdated(String name, FilterContext context) {
225 if (name.equals("framerate")) {
226 getCameraParameters();
227 int closestRange[] = findClosestFpsRange(mFps, mCameraParameters);
228 mCameraParameters.setPreviewFpsRange(closestRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX],
229 closestRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);
230 mCamera.setParameters(mCameraParameters);
234 synchronized public Camera.Parameters getCameraParameters() {
235 boolean closeCamera = false;
236 if (mCameraParameters == null) {
237 if (mCamera == null) {
238 mCamera = Camera.open(mCameraId);
241 mCameraParameters = mCamera.getParameters();
249 int closestSize[] = findClosestSize(mWidth, mHeight, mCameraParameters);
250 mWidth = closestSize[0];
251 mHeight = closestSize[1];
252 mCameraParameters.setPreviewSize(mWidth, mHeight);
254 int closestRange[] = findClosestFpsRange(mFps, mCameraParameters);
256 mCameraParameters.setPreviewFpsRange(closestRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX],
257 closestRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);
259 return mCameraParameters;
262 /** Update camera parameters. Image resolution cannot be changed. */
263 synchronized public void setCameraParameters(Camera.Parameters params) {
264 params.setPreviewSize(mWidth, mHeight);
265 mCameraParameters = params;
267 mCamera.setParameters(mCameraParameters);
271 private int[] findClosestSize(int width, int height, Camera.Parameters parameters) {
272 List<Camera.Size> previewSizes = parameters.getSupportedPreviewSizes();
273 int closestWidth = -1;
274 int closestHeight = -1;
275 int smallestWidth = previewSizes.get(0).width;
276 int smallestHeight = previewSizes.get(0).height;
277 for (Camera.Size size : previewSizes) {
278 // Best match defined as not being larger in either dimension than
279 // the requested size, but as close as possible. The below isn't a
280 // stable selection (reording the size list can give different
281 // results), but since this is a fallback nicety, that's acceptable.
282 if ( size.width < width &&
283 size.height < height &&
284 size.width >= closestWidth &&
285 size.height >= closestHeight) {
286 closestWidth = size.width;
287 closestHeight = size.height;
289 if ( size.width < smallestWidth &&
290 size.height < smallestHeight) {
291 smallestWidth = size.width;
292 smallestHeight = size.height;
295 if (closestWidth == -1) {
296 // Requested size is smaller than any listed size; match with smallest possible
297 closestWidth = smallestWidth;
298 closestHeight = smallestHeight;
303 "Requested resolution: (" + width + ", " + height
304 + "). Closest match: (" + closestWidth + ", "
305 + closestHeight + ").");
307 int[] closestSize = {closestWidth, closestHeight};
311 private int[] findClosestFpsRange(int fps, Camera.Parameters params) {
312 List<int[]> supportedFpsRanges = params.getSupportedPreviewFpsRange();
313 int[] closestRange = supportedFpsRanges.get(0);
314 for (int[] range : supportedFpsRanges) {
315 if (range[Camera.Parameters.PREVIEW_FPS_MIN_INDEX] < fps*1000 &&
316 range[Camera.Parameters.PREVIEW_FPS_MAX_INDEX] > fps*1000 &&
317 range[Camera.Parameters.PREVIEW_FPS_MIN_INDEX] >
318 closestRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX] &&
319 range[Camera.Parameters.PREVIEW_FPS_MAX_INDEX] <
320 closestRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]) {
321 closestRange = range;
324 if (mLogVerbose) Log.v(TAG, "Requested fps: " + fps
325 + ".Closest frame rate range: ["
326 + closestRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX] / 1000.
328 + closestRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX] / 1000.
334 private SurfaceTexture.OnFrameAvailableListener onCameraFrameAvailableListener =
335 new SurfaceTexture.OnFrameAvailableListener() {
337 public void onFrameAvailable(SurfaceTexture surfaceTexture) {
338 if (mLogVerbose) Log.v(TAG, "New frame from camera");
339 synchronized(CameraSource.this) {
340 mNewFrameAvailable = true;
341 CameraSource.this.notify();