OSDN Git Service

Concurrency fix to CameraSource.
[android-x86/system-media.git] / mca / filterpacks / videosrc / java / CameraSource.java
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17
18 package android.filterpacks.videosrc;
19
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;
38
39 import java.io.IOException;
40 import java.util.List;
41 import java.util.Set;
42
43 import android.util.Log;
44
45 /**
46  * @hide
47  */
48 public class CameraSource extends Filter {
49
50     /** User-visible parameters */
51
52     /** Camera ID to use for input. Defaults to 0. */
53     @GenerateFieldPort(name = "id", hasDefault = true)
54     private int mCameraId = 0;
55
56     /** Frame width to request from camera. Actual size may not match requested. */
57     @GenerateFieldPort(name = "width", hasDefault = true)
58     private int mWidth = 320;
59
60     /** Frame height to request from camera. Actual size may not match requested. */
61     @GenerateFieldPort(name = "height", hasDefault = true)
62     private int mHeight = 240;
63
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;
67
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.
72      */
73     @GenerateFinalPort(name = "waitForNewFrame", hasDefault = true)
74     private boolean mWaitForNewFrame = true;
75
76     private Camera mCamera;
77     private GLFrame mCameraFrame;
78     private SurfaceTexture mSurfaceTexture;
79     private ShaderProgram mFrameExtractor;
80     private MutableFrameFormat mOutputFormat;
81     private float[] mCameraTransform;
82
83     private static final int NEWFRAME_TIMEOUT = 100; //ms
84     private static final int NEWFRAME_TIMEOUT_REPEAT = 10;
85
86     private boolean mNewFrameAvailable;
87
88     private Camera.Parameters mCameraParameters;
89
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" +
96             "void main() {\n" +
97             "  vec2 transformed_texcoord = (camera_transform * vec4(v_texcoord, 0., 1.) ).xy;" +
98             "  gl_FragColor = texture2D(tex_sampler_0, transformed_texcoord);\n" +
99             "}\n";
100
101     private final boolean mLogVerbose;
102     private static final String TAG = "CameraSource";
103
104     public CameraSource(String name) {
105         super(name);
106         mCameraTransform = new float[16];
107
108         mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
109     }
110
111     @Override
112     public void setupPorts() {
113         // Add input port
114         addOutputPort("video", ImageFormat.create(ImageFormat.COLORSPACE_RGBA,
115                                                   FrameFormat.TARGET_GPU));
116     }
117
118     private void createFormats() {
119         mOutputFormat = ImageFormat.create(mWidth, mHeight,
120                                            ImageFormat.COLORSPACE_RGBA,
121                                            FrameFormat.TARGET_GPU);
122     }
123
124     @Override
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);
132     }
133
134     @Override
135     public void open(FilterContext context) {
136         if (mLogVerbose) Log.v(TAG, "Opening");
137         // Open camera
138         mCamera = Camera.open(mCameraId);
139
140         // Set parameters
141         getCameraParameters();
142         mCamera.setParameters(mCameraParameters);
143
144         // Create frame formats
145         createFormats();
146
147         // Bind it to our camera frame
148         mCameraFrame = (GLFrame)context.getFrameManager().newBoundFrame(mOutputFormat,
149                                                                         GLFrame.EXTERNAL_TEXTURE,
150                                                                         0);
151         mSurfaceTexture = new SurfaceTexture(mCameraFrame.getTextureId());
152         try {
153             mCamera.setPreviewTexture(mSurfaceTexture);
154         } catch (IOException e) {
155             throw new RuntimeException("Could not bind camera surface texture: " +
156                                        e.getMessage() + "!");
157         }
158
159         // Connect SurfaceTexture to callback
160         mSurfaceTexture.setOnFrameAvailableListener(onCameraFrameAvailableListener);
161         // Start the preview
162         mNewFrameAvailable = false;
163         mCamera.startPreview();
164     }
165
166     @Override
167     public void process(FilterContext context) {
168         if (mLogVerbose) Log.v(TAG, "Processing new frame");
169
170         if (mWaitForNewFrame) {
171             int waitCount = 0;
172             while (!mNewFrameAvailable) {
173                 if (waitCount == NEWFRAME_TIMEOUT_REPEAT) {
174                     throw new RuntimeException("Timeout waiting for new frame");
175                 }
176                 try {
177                     this.wait(NEWFRAME_TIMEOUT);
178                 } catch (InterruptedException e) {
179                     if (mLogVerbose) Log.v(TAG, "Interrupted while waiting for new frame");
180                 }
181             }
182             mNewFrameAvailable = false;
183             if (mLogVerbose) Log.v(TAG, "Got new frame");
184         }
185
186         mSurfaceTexture.updateTexImage();
187
188         if (mLogVerbose) Log.v(TAG, "Using frame extractor in thread: " + Thread.currentThread());
189         mSurfaceTexture.getTransformMatrix(mCameraTransform);
190         mFrameExtractor.setHostValue("camera_transform", mCameraTransform);
191
192         Frame output = context.getFrameManager().newFrame(mOutputFormat);
193         mFrameExtractor.process(mCameraFrame, output);
194
195         long timestamp = mSurfaceTexture.getTimestamp();
196         if (mLogVerbose) Log.v(TAG, "Timestamp: " + (timestamp / 1000000000.0) + " s");
197         output.setTimestamp(timestamp);
198
199         pushOutput("video", output);
200
201         // Release pushed frame
202         output.release();
203
204         if (mLogVerbose) Log.v(TAG, "Done processing new frame");
205     }
206
207     @Override
208     public void close(FilterContext context) {
209         if (mLogVerbose) Log.v(TAG, "Closing");
210
211         mCamera.release();
212         mCamera = null;
213         mSurfaceTexture = null;
214     }
215
216     @Override
217     public void tearDown(FilterContext context) {
218         if (mCameraFrame != null) {
219             mCameraFrame.release();
220         }
221     }
222
223     @Override
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);
231         }
232     }
233
234     synchronized public Camera.Parameters getCameraParameters() {
235         boolean closeCamera = false;
236         if (mCameraParameters == null) {
237             if (mCamera == null) {
238                 mCamera = Camera.open(mCameraId);
239                 closeCamera = true;
240             }
241             mCameraParameters = mCamera.getParameters();
242
243             if (closeCamera) {
244                 mCamera.release();
245                 mCamera = null;
246             }
247         }
248
249         int closestSize[] = findClosestSize(mWidth, mHeight, mCameraParameters);
250         mWidth = closestSize[0];
251         mHeight = closestSize[1];
252         mCameraParameters.setPreviewSize(mWidth, mHeight);
253
254         int closestRange[] = findClosestFpsRange(mFps, mCameraParameters);
255
256         mCameraParameters.setPreviewFpsRange(closestRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX],
257                                              closestRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);
258
259         return mCameraParameters;
260     }
261
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;
266         if (isOpen()) {
267             mCamera.setParameters(mCameraParameters);
268         }
269     }
270
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;
288             }
289             if ( size.width < smallestWidth &&
290                  size.height < smallestHeight) {
291                 smallestWidth = size.width;
292                 smallestHeight = size.height;
293             }
294         }
295         if (closestWidth == -1) {
296             // Requested size is smaller than any listed size; match with smallest possible
297             closestWidth = smallestWidth;
298             closestHeight = smallestHeight;
299         }
300
301         if (mLogVerbose) {
302             Log.v(TAG,
303                   "Requested resolution: (" + width + ", " + height
304                   + "). Closest match: (" + closestWidth + ", "
305                   + closestHeight + ").");
306         }
307         int[] closestSize = {closestWidth, closestHeight};
308         return closestSize;
309     }
310
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;
322             }
323         }
324         if (mLogVerbose) Log.v(TAG, "Requested fps: " + fps
325                                + ".Closest frame rate range: ["
326                                + closestRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX] / 1000.
327                                + ","
328                                + closestRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX] / 1000.
329                                + "]");
330
331         return closestRange;
332     }
333
334     private SurfaceTexture.OnFrameAvailableListener onCameraFrameAvailableListener =
335             new SurfaceTexture.OnFrameAvailableListener() {
336         @Override
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();
342             }
343         }
344     };
345
346 }