ItsService.java revision d5a1394a36b958e81c20f8be8e2f1f72626d82da
1/*
2 * Copyright (C) 2013 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
17package com.android.camera2.its;
18
19import android.app.Service;
20import android.content.Context;
21import android.content.Intent;
22import android.graphics.ImageFormat;
23import android.hardware.camera2.CameraAccessException;
24import android.hardware.camera2.CameraDevice;
25import android.hardware.camera2.CameraManager;
26import android.hardware.camera2.CameraProperties;
27import android.hardware.camera2.CaptureRequest;
28import android.hardware.camera2.CaptureResult;
29import android.media.Image;
30import android.media.ImageReader;
31import android.net.Uri;
32import android.os.ConditionVariable;
33import android.os.Handler;
34import android.os.HandlerThread;
35import android.os.IBinder;
36import android.os.Message;
37import android.util.Log;
38import android.view.Surface;
39
40import org.json.JSONObject;
41
42import java.io.File;
43import java.nio.ByteBuffer;
44import java.util.ArrayList;
45import java.util.Arrays;
46import java.util.List;
47import java.util.concurrent.CountDownLatch;
48import java.util.concurrent.TimeUnit;
49
50public class ItsService extends Service {
51    public static final String TAG = ItsService.class.getSimpleName();
52    public static final String PYTAG = "CAMERA-ITS-PY";
53
54    // Supported intents
55    public static final String ACTION_CAPTURE = "com.android.camera2.its.CAPTURE";
56    public static final String ACTION_3A = "com.android.camera2.its.3A";
57    public static final String ACTION_GETPROPS = "com.android.camera2.its.GETPROPS";
58    private static final int MESSAGE_CAPTURE = 1;
59    private static final int MESSAGE_3A = 2;
60    private static final int MESSAGE_GETPROPS = 3;
61
62    // Timeouts, in seconds.
63    public static final int TIMEOUT_CAPTURE = 10;
64    public static final int TIMEOUT_3A = 10;
65
66    private static final int MAX_CONCURRENT_READER_BUFFERS = 8;
67
68    public static final String REGION_KEY = "regions";
69    public static final String REGION_AE_KEY = "ae";
70    public static final String REGION_AWB_KEY = "awb";
71    public static final String REGION_AF_KEY = "af";
72
73    private CameraManager mCameraManager = null;
74    private CameraDevice mCamera = null;
75    private ImageReader mCaptureReader = null;
76    private CameraProperties mCameraProperties = null;
77
78    private HandlerThread mCommandThread;
79    private Handler mCommandHandler;
80    private HandlerThread mSaveThread;
81    private Handler mSaveHandler;
82
83    private ConditionVariable mInterlock3A = new ConditionVariable(true);
84    private volatile boolean mIssuedRequest3A = false;
85    private volatile boolean mConvergedAE = false;
86    private volatile boolean mConvergedAF = false;
87    private volatile boolean mConvergedAWB = false;
88
89    private CountDownLatch mCaptureCallbackLatch;
90
91    public interface CaptureListener {
92        void onCaptureAvailable(Image capture);
93    }
94
95    public abstract class CaptureResultListener extends CameraDevice.CaptureListener {}
96
97    @Override
98    public IBinder onBind(Intent intent) {
99        return null;
100    }
101
102    @Override
103    public void onCreate() {
104
105        try {
106            // Get handle to camera manager.
107            mCameraManager = (CameraManager) this.getSystemService(Context.CAMERA_SERVICE);
108            if (mCameraManager == null) {
109                throw new ItsException("Failed to connect to camera manager");
110            }
111
112            // Open the camera device, and get its properties.
113            String[] devices;
114            try {
115                devices = mCameraManager.getCameraIdList();
116                if (devices == null || devices.length == 0) {
117                    throw new ItsException("No camera devices");
118                }
119
120                // TODO: Add support for specifying which device to open.
121                mCamera = mCameraManager.openCamera(devices[0]);
122                mCameraProperties = mCamera.getProperties();
123            } catch (CameraAccessException e) {
124                throw new ItsException("Failed to get device ID list");
125            }
126
127            // Create a thread to receive images and save them.
128            mSaveThread = new HandlerThread("SaveThread");
129            mSaveThread.start();
130            mSaveHandler = new Handler(mSaveThread.getLooper());
131
132            // Create a thread to process commands.
133            mCommandThread = new HandlerThread("CaptureThread");
134            mCommandThread.start();
135            mCommandHandler = new Handler(mCommandThread.getLooper(), new Handler.Callback() {
136                @Override
137                public boolean handleMessage(Message msg) {
138                    try {
139                        switch (msg.what) {
140                            case MESSAGE_CAPTURE:
141                                doCapture((Uri) msg.obj);
142                                break;
143                            case MESSAGE_3A:
144                                do3A((Uri) msg.obj);
145                                break;
146                            case MESSAGE_GETPROPS:
147                                doGetProps();
148                                break;
149                            default:
150                                throw new ItsException("Unknown message type");
151                        }
152                        Log.i(PYTAG, "### DONE");
153                        return true;
154                    }
155                    catch (ItsException e) {
156                        Log.e(TAG, "Script failed: ", e);
157                        Log.e(PYTAG, "### FAIL");
158                        return true;
159                    }
160                }
161            });
162        } catch (ItsException e) {
163            Log.e(TAG, "Script failed: ", e);
164            Log.e(PYTAG, "### FAIL");
165        }
166    }
167
168    @Override
169    public void onDestroy() {
170        try {
171            if (mCommandThread != null) {
172                mCommandThread.quit();
173                mCommandThread = null;
174            }
175            if (mSaveThread != null) {
176                mSaveThread.quit();
177                mSaveThread = null;
178            }
179
180            try {
181                mCamera.close();
182            } catch (Exception e) {
183                throw new ItsException("Failed to close device");
184            }
185        } catch (ItsException e) {
186            Log.e(TAG, "Script failed: ", e);
187            Log.e(PYTAG, "### FAIL");
188        }
189    }
190
191    @Override
192    public int onStartCommand(Intent intent, int flags, int startId) {
193        try {
194            Log.i(PYTAG, "### RECV");
195            String action = intent.getAction();
196            if (ACTION_CAPTURE.equals(action)) {
197                Uri uri = intent.getData();
198                Message m = mCommandHandler.obtainMessage(MESSAGE_CAPTURE, uri);
199                mCommandHandler.sendMessage(m);
200            } else if (ACTION_3A.equals(action)) {
201                Uri uri = intent.getData();
202                Message m = mCommandHandler.obtainMessage(MESSAGE_3A, uri);
203                mCommandHandler.sendMessage(m);
204            } else if (ACTION_GETPROPS.equals(action)) {
205                Uri uri = intent.getData();
206                Message m = mCommandHandler.obtainMessage(MESSAGE_GETPROPS, uri);
207                mCommandHandler.sendMessage(m);
208            } else {
209                throw new ItsException("Unhandled intent: " + intent.toString());
210            }
211        } catch (ItsException e) {
212            Log.e(TAG, "Script failed: ", e);
213            Log.e(PYTAG, "### FAIL");
214        }
215        return START_STICKY;
216    }
217
218    public void idleCamera() throws ItsException {
219        try {
220            mCamera.stopRepeating();
221            mCamera.waitUntilIdle();
222        } catch (CameraAccessException e) {
223            throw new ItsException("Error waiting for camera idle", e);
224        }
225    }
226
227    private ImageReader.OnImageAvailableListener
228            createAvailableListener(final CaptureListener listener) {
229        return new ImageReader.OnImageAvailableListener() {
230            @Override
231            public void onImageAvailable(ImageReader reader) {
232                Image i = null;
233                try {
234                    i = reader.acquireNextImage();
235                    listener.onCaptureAvailable(i);
236                } catch(ImageReader.MaxImagesAcquiredException e) {
237                    throw new IllegalStateException(e);
238                } finally {
239                    if (i != null) {
240                        i.close();
241                    }
242                }
243            }
244        };
245    }
246
247    private ImageReader.OnImageAvailableListener
248            createAvailableListenerDropper(final CaptureListener listener) {
249        return new ImageReader.OnImageAvailableListener() {
250            @Override
251            public void onImageAvailable(ImageReader reader) {
252                try {
253                    Image i = reader.acquireNextImage();
254                    i.close();
255                } catch (ImageReader.MaxImagesAcquiredException e) {
256                    // OK: don't need to drop it if we couldn't acquire it.
257                }
258            }
259        };
260    }
261
262    private void doGetProps() throws ItsException {
263        String fileName = ItsUtils.getMetadataFileName(0);
264        File mdFile = ItsUtils.getOutputFile(ItsService.this, fileName);
265        ItsUtils.storeCameraProperties(mCameraProperties, mdFile);
266        Log.i(PYTAG,
267              String.format("### FILE %s",
268                            ItsUtils.getExternallyVisiblePath(ItsService.this, mdFile.toString())));
269    }
270
271    private void prepareCaptureReader(int width, int height, int format) {
272        if (mCaptureReader == null
273                || mCaptureReader.getWidth() != width
274                || mCaptureReader.getHeight() != height
275                || mCaptureReader.getImageFormat() != format) {
276            if (mCaptureReader != null) {
277                mCaptureReader.close();
278            }
279            mCaptureReader = ImageReader.newInstance(width, height, format,
280                    MAX_CONCURRENT_READER_BUFFERS);
281        }
282    }
283
284    private void do3A(Uri uri) throws ItsException {
285        try {
286            if (uri == null || !uri.toString().endsWith(".json")) {
287                throw new ItsException("Invalid URI: " + uri);
288            }
289
290            idleCamera();
291
292            // Start a 3A action, and wait for it to converge.
293            // Get the converged values for each "A", and package into JSON result for caller.
294
295            // 3A happens on full-res frames.
296            android.hardware.camera2.Size sizes[] = mCameraProperties.get(
297                    CameraProperties.SCALER_AVAILABLE_JPEG_SIZES);
298            int width = sizes[0].getWidth();
299            int height = sizes[0].getHeight();
300            int format = ImageFormat.YUV_420_888;
301
302            prepareCaptureReader(width, height, format);
303            List<Surface> outputSurfaces = new ArrayList<Surface>(1);
304            outputSurfaces.add(mCaptureReader.getSurface());
305            mCamera.configureOutputs(outputSurfaces);
306
307            // Add a listener that just recycles buffers; they aren't saved anywhere.
308            ImageReader.OnImageAvailableListener readerListener =
309                    createAvailableListenerDropper(mCaptureListener);
310            mCaptureReader.setOnImageAvailableListener(readerListener, mSaveHandler);
311
312            // Get the user-specified regions for AE, AWB, AF.
313            // Note that the user specifies normalized [x,y,w,h], which is converted below
314            // to an [x0,y0,x1,y1] region in sensor coords. The capture request region
315            // also has a fifth "weight" element: [x0,y0,x1,y1,w].
316            int[] regionAE = new int[]{0,0,width-1,height-1,1};
317            int[] regionAF = new int[]{0,0,width-1,height-1,1};
318            int[] regionAWB = new int[]{0,0,width-1,height-1,1};
319            JSONObject params = ItsUtils.loadJsonFile(uri);
320            if (params.has(REGION_KEY)) {
321                JSONObject regions = params.getJSONObject(REGION_KEY);
322                if (params.has(REGION_AE_KEY)) {
323                    int[] r = ItsUtils.getJsonRectFromArray(
324                            params.getJSONArray(REGION_AE_KEY), true, width, height);
325                    regionAE = new int[]{r[0],r[1],r[0]+r[2]-1,r[1]+r[3]-1,1};
326                }
327                if (params.has(REGION_AF_KEY)) {
328                    int[] r = ItsUtils.getJsonRectFromArray(
329                            params.getJSONArray(REGION_AF_KEY), true, width, height);
330                    regionAF = new int[]{r[0],r[1],r[0]+r[2]-1,r[1]+r[3]-1,1};
331                }
332                if (params.has(REGION_AWB_KEY)) {
333                    int[] r = ItsUtils.getJsonRectFromArray(
334                            params.getJSONArray(REGION_AWB_KEY), true, width, height);
335                    regionAWB = new int[]{r[0],r[1],r[0]+r[2]-1,r[1]+r[3]-1,1};
336                }
337            }
338            Log.i(TAG, "AE region: " + Arrays.toString(regionAE));
339            Log.i(TAG, "AF region: " + Arrays.toString(regionAF));
340            Log.i(TAG, "AWB region: " + Arrays.toString(regionAWB));
341
342            mInterlock3A.open();
343            mIssuedRequest3A = false;
344            mConvergedAE = false;
345            mConvergedAWB = false;
346            mConvergedAF = false;
347            long tstart = System.currentTimeMillis();
348            boolean triggeredAE = false;
349            boolean triggeredAF = false;
350
351            // Keep issuing capture requests until 3A has converged.
352            // First do AE, then do AF and AWB together.
353            while (true) {
354
355                // Block until can take the next 3A frame. Only want one outstanding frame
356                // at a time, to simplify the logic here.
357                if (!mInterlock3A.block(TIMEOUT_3A * 1000) ||
358                        System.currentTimeMillis() - tstart > TIMEOUT_3A * 1000) {
359                    throw new ItsException("3A failed to converge (timeout)");
360                }
361                mInterlock3A.close();
362
363                // If not converged yet, issue another capture request.
364                if (!mConvergedAE || !mConvergedAWB || !mConvergedAF) {
365
366                    // Baseline capture request for 3A.
367                    CaptureRequest.Builder req = mCamera.createCaptureRequest(
368                            CameraDevice.TEMPLATE_PREVIEW);
369                    req.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
370                    req.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO);
371                    req.set(CaptureRequest.CONTROL_CAPTURE_INTENT,
372                            CaptureRequest.CONTROL_CAPTURE_INTENT_PREVIEW);
373                    req.set(CaptureRequest.CONTROL_AE_MODE,
374                            CaptureRequest.CONTROL_AE_MODE_ON);
375                    req.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, 0);
376                    req.set(CaptureRequest.CONTROL_AE_LOCK, false);
377                    req.set(CaptureRequest.CONTROL_AE_REGIONS, regionAE);
378                    req.set(CaptureRequest.CONTROL_AF_MODE,
379                            CaptureRequest.CONTROL_AF_MODE_AUTO);
380                    req.set(CaptureRequest.CONTROL_AF_REGIONS, regionAF);
381                    req.set(CaptureRequest.CONTROL_AWB_MODE,
382                            CaptureRequest.CONTROL_AWB_MODE_AUTO);
383                    req.set(CaptureRequest.CONTROL_AWB_LOCK, false);
384                    req.set(CaptureRequest.CONTROL_AWB_REGIONS, regionAWB);
385
386                    // Trigger AE first.
387                    if (!triggeredAE) {
388                        Log.i(TAG, "Triggering AE");
389                        req.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
390                                CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
391                        triggeredAE = true;
392                    }
393
394                    // After AE has converged, trigger AF.
395                    if (!triggeredAF && triggeredAE && mConvergedAE) {
396                        Log.i(TAG, "Triggering AF");
397                        req.set(CaptureRequest.CONTROL_AF_TRIGGER,
398                                CaptureRequest.CONTROL_AF_TRIGGER_START);
399                        triggeredAF = true;
400                    }
401
402                    req.addTarget(mCaptureReader.getSurface());
403
404                    mIssuedRequest3A = true;
405                    mCamera.capture(req.build(), mCaptureResultListener, null);
406                } else {
407                    Log.i(TAG, "3A converged");
408                    break;
409                }
410            }
411        } catch (android.hardware.camera2.CameraAccessException e) {
412            throw new ItsException("Access error: ", e);
413        } catch (org.json.JSONException e) {
414            throw new ItsException("JSON error: ", e);
415        }
416    }
417
418    private void doCapture(Uri uri) throws ItsException {
419        try {
420            if (uri == null || !uri.toString().endsWith(".json")) {
421                throw new ItsException("Invalid URI: " + uri);
422            }
423
424            idleCamera();
425
426            // Parse the JSON to get the list of capture requests.
427            List<CaptureRequest.Builder> requests = ItsUtils.loadRequestList(mCamera, uri);
428
429            // Set the output surface and listeners.
430            try {
431                // Default:
432                // Capture full-frame images. Use the reported JPEG size rather than the sensor
433                // size since this is more likely to be the unscaled size; the crop from sensor
434                // size is probably for the ISP (e.g. demosaicking) rather than the encoder.
435                android.hardware.camera2.Size sizes[] = mCameraProperties.get(
436                        CameraProperties.SCALER_AVAILABLE_JPEG_SIZES);
437                int width = sizes[0].getWidth();
438                int height = sizes[0].getHeight();
439                int format = ImageFormat.YUV_420_888;
440
441                JSONObject jsonOutputSpecs = ItsUtils.getOutputSpecs(uri);
442                if (jsonOutputSpecs != null) {
443                    // Use the user's JSON capture spec.
444                    int width2 = jsonOutputSpecs.optInt("width");
445                    int height2 = jsonOutputSpecs.optInt("height");
446                    if (width2 > 0) {
447                        width = width2;
448                    }
449                    if (height2 > 0) {
450                        height = height2;
451                    }
452                    String sformat = jsonOutputSpecs.optString("format");
453                    if ("yuv".equals(sformat)) {
454                        format = ImageFormat.YUV_420_888;
455                    } else if ("jpg".equals(sformat) || "jpeg".equals(sformat)) {
456                        format = ImageFormat.JPEG;
457                    } else if ("".equals(sformat)) {
458                        // No format specified.
459                    } else {
460                        throw new ItsException("Unsupported format: " + sformat);
461                    }
462                }
463
464                Log.i(PYTAG, String.format("### SIZE %d %d", width, height));
465
466                prepareCaptureReader(width, height, format);
467                List<Surface> outputSurfaces = new ArrayList<Surface>(1);
468                outputSurfaces.add(mCaptureReader.getSurface());
469                mCamera.configureOutputs(outputSurfaces);
470
471                ImageReader.OnImageAvailableListener readerListener =
472                        createAvailableListener(mCaptureListener);
473                mCaptureReader.setOnImageAvailableListener(readerListener, mSaveHandler);
474
475                // Plan for how many callbacks need to be received throughout the duration of this
476                // sequence of capture requests.
477                int numCaptures = requests.size();
478                mCaptureCallbackLatch = new CountDownLatch(
479                        numCaptures * ItsUtils.getCallbacksPerCapture(format));
480
481            } catch (CameraAccessException e) {
482                throw new ItsException("Error configuring outputs", e);
483            }
484
485            // Initiate the captures.
486            for (int i = 0; i < requests.size(); i++) {
487                CaptureRequest.Builder req = requests.get(i);
488                Log.i(PYTAG, String.format("### CAPT %d of %d", i+1, requests.size()));
489                req.addTarget(mCaptureReader.getSurface());
490                mCamera.capture(req.build(), mCaptureResultListener, null);
491            }
492
493            // Make sure all callbacks have been hit (wait until captures are done).
494            try {
495                if (!mCaptureCallbackLatch.await(TIMEOUT_CAPTURE, TimeUnit.SECONDS)) {
496                    throw new ItsException(
497                            "Timeout hit, but all callbacks not received");
498                }
499            } catch (InterruptedException e) {
500                throw new ItsException("Interrupted: ", e);
501            }
502
503        } catch (android.hardware.camera2.CameraAccessException e) {
504            throw new ItsException("Access error: ", e);
505        }
506    }
507
508    private final CaptureListener mCaptureListener = new CaptureListener() {
509        @Override
510        public void onCaptureAvailable(Image capture) {
511            try {
512                int format = capture.getFormat();
513                String extFileName = null;
514                if (format == ImageFormat.JPEG) {
515                    String fileName = ItsUtils.getJpegFileName(capture.getTimestamp());
516                    ByteBuffer buf = capture.getPlanes()[0].getBuffer();
517                    extFileName = ItsUtils.writeImageToFile(ItsService.this, buf, fileName);
518                } else if (format == ImageFormat.YUV_420_888) {
519                    String fileName = ItsUtils.getYuvFileName(capture.getTimestamp());
520                    byte[] img = ItsUtils.getDataFromImage(capture);
521                    ByteBuffer buf = ByteBuffer.wrap(img);
522                    extFileName = ItsUtils.writeImageToFile(ItsService.this, buf, fileName);
523                } else {
524                    throw new ItsException("Unsupported image format: " + format);
525                }
526                Log.i(PYTAG, String.format("### FILE %s", extFileName));
527                mCaptureCallbackLatch.countDown();
528            } catch (ItsException e) {
529                Log.e(TAG, "Script error: " + e);
530                Log.e(PYTAG, "### FAIL");
531            }
532        }
533    };
534
535    private final CaptureResultListener mCaptureResultListener = new CaptureResultListener() {
536        @Override
537        public void onCaptureStarted(CameraDevice camera, CaptureRequest request, long timestamp) {
538        }
539
540        @Override
541        public void onCaptureCompleted(CameraDevice camera, CaptureRequest request,
542                CaptureResult result) {
543            try {
544                // Currently result has all 0 values.
545                if (request == null || result == null) {
546                    throw new ItsException("Request/result is invalid");
547                }
548
549                Log.i(TAG, String.format(
550                        "Capture result: AE=%d, AF=%d, AWB=%d, sens=%d, exp=%dms, dur=%dms",
551                         result.get(CaptureResult.CONTROL_AE_STATE),
552                         result.get(CaptureResult.CONTROL_AF_STATE),
553                         result.get(CaptureResult.CONTROL_AWB_STATE),
554                         result.get(CaptureResult.SENSOR_SENSITIVITY),
555                         result.get(CaptureResult.SENSOR_EXPOSURE_TIME).intValue(),
556                         result.get(CaptureResult.SENSOR_FRAME_DURATION).intValue()));
557
558                mConvergedAE = result.get(CaptureResult.CONTROL_AE_STATE) ==
559                                          CaptureResult.CONTROL_AE_STATE_CONVERGED;
560                mConvergedAF = result.get(CaptureResult.CONTROL_AF_STATE) ==
561                                          CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED;
562                mConvergedAWB = result.get(CaptureResult.CONTROL_AWB_STATE) ==
563                                           CaptureResult.CONTROL_AWB_STATE_CONVERGED;
564
565                if (mIssuedRequest3A) {
566                    mIssuedRequest3A = false;
567                    mInterlock3A.open();
568                } else {
569                    String fileName = ItsUtils.getMetadataFileName(
570                            result.get(CaptureResult.SENSOR_TIMESTAMP));
571                    File mdFile = ItsUtils.getOutputFile(ItsService.this, fileName);
572                    ItsUtils.storeResults(mCameraProperties, request, result, mdFile);
573                    mCaptureCallbackLatch.countDown();
574                }
575            } catch (ItsException e) {
576                Log.e(TAG, "Script error: " + e);
577                Log.e(PYTAG, "### FAIL");
578            } catch (Exception e) {
579                Log.e(TAG, "Script error: " + e);
580                Log.e(PYTAG, "### FAIL");
581            }
582        }
583
584        @Override
585        public void onCaptureFailed(CameraDevice camera, CaptureRequest request) {
586            mCaptureCallbackLatch.countDown();
587            Log.e(TAG, "Script error: capture failed");
588            Log.e(PYTAG, "### FAIL");
589        }
590    };
591
592}
593