• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * iCopyright 2021 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 package android.hardware.camera2.cts;
18 
19 import static android.hardware.camera2.cts.CameraTestUtils.*;
20 
21 import android.graphics.ImageFormat;
22 import android.media.Image;
23 import android.media.ImageReader;
24 import android.media.ImageWriter;
25 import android.hardware.camera2.CameraCharacteristics;
26 import android.hardware.camera2.CameraCaptureSession;
27 import android.hardware.camera2.CameraDevice;
28 import android.hardware.camera2.CaptureFailure;
29 import android.hardware.camera2.CaptureRequest;
30 import android.hardware.camera2.CaptureResult;
31 import android.hardware.camera2.MultiResolutionImageReader;
32 import android.hardware.camera2.TotalCaptureResult;
33 import android.hardware.camera2.cts.helpers.StaticMetadata;
34 import android.hardware.camera2.cts.helpers.StaticMetadata.CheckLevel;
35 import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
36 import android.hardware.camera2.params.MandatoryStreamCombination;
37 import android.hardware.camera2.params.MandatoryStreamCombination.MandatoryStreamInformation;
38 import android.hardware.camera2.params.MultiResolutionStreamConfigurationMap;
39 import android.hardware.camera2.params.MultiResolutionStreamInfo;
40 import android.hardware.camera2.params.InputConfiguration;
41 import android.hardware.camera2.params.OutputConfiguration;
42 import android.hardware.camera2.params.SessionConfiguration;
43 import android.hardware.camera2.params.StreamConfigurationMap;
44 import android.util.Log;
45 import android.util.Size;
46 import android.view.Surface;
47 import android.view.SurfaceHolder;
48 
49 import com.android.ex.camera2.blocking.BlockingSessionCallback;
50 
51 import java.util.Arrays;
52 import java.util.ArrayList;
53 import java.util.Collection;
54 import java.util.List;
55 import java.util.Map;
56 import java.util.Set;
57 
58 import org.junit.runners.Parameterized;
59 import org.junit.runner.RunWith;
60 import org.junit.Test;
61 import static org.mockito.Mockito.*;
62 
63 /**
64  * Tests for multi-resolution size reprocessing.
65  */
66 
67 @RunWith(Parameterized.class)
68 public class MultiResolutionReprocessCaptureTest extends Camera2AndroidTestCase  {
69     private static final String TAG = "MultiResolutionReprocessCaptureTest";
70     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
71     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
72     private static final int CAPTURE_TIMEOUT_FRAMES = 100;
73     private static final int CAPTURE_TIMEOUT_MS = 3000;
74     private static final int WAIT_FOR_SURFACE_CHANGE_TIMEOUT_MS = 1000;
75     private static final int CAPTURE_TEMPLATE = CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG;
76     private int mDumpFrameCount = 0;
77 
78     // The image reader for the regular captures
79     private MultiResolutionImageReader mMultiResImageReader;
80     // The image reader for the reprocess capture
81     private MultiResolutionImageReader mSecondMultiResImageReader;
82     // A flag indicating whether the regular capture and the reprocess capture share the same
83     // multi-resolution image reader. If it's true, the mMultiResImageReader should be used for
84     // both regular and reprocess outputs.
85     private boolean mShareOneReader;
86     private SimpleMultiResolutionImageReaderListener mMultiResImageReaderListener;
87     private SimpleMultiResolutionImageReaderListener mSecondMultiResImageReaderListener;
88     private Surface mInputSurface;
89     private ImageWriter mImageWriter;
90     private SimpleImageWriterListener mImageWriterListener;
91 
92     @Test
testMultiResolutionReprocessCharacteristics()93     public void testMultiResolutionReprocessCharacteristics() throws Exception {
94         for (String id : getCameraIdsUnderTest()) {
95             if (VERBOSE) {
96                 Log.v(TAG, "Testing multi-resolution reprocess characteristics for Camera " + id);
97             }
98             StaticMetadata info = mAllStaticInfo.get(id);
99             CameraCharacteristics c = info.getCharacteristics();
100             StreamConfigurationMap config = c.get(
101                     CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
102             int[] inputFormats = config.getInputFormats();
103             int[] capabilities = CameraTestUtils.getValueNotNull(
104                     c, CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
105             boolean isLogicalCamera = CameraTestUtils.contains(capabilities,
106                     CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA);
107             boolean isUltraHighResCamera = info.isUltraHighResolutionSensor();
108             Set<String> physicalCameraIds = c.getPhysicalCameraIds();
109 
110             MultiResolutionStreamConfigurationMap multiResolutionMap = c.get(
111                     CameraCharacteristics.SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP);
112             if (multiResolutionMap == null) {
113                 Log.i(TAG, "Camera " + id + " doesn't support multi-resolution reprocessing.");
114                 continue;
115             }
116             if (VERBOSE) {
117                 Log.v(TAG, "MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP: "
118                         + multiResolutionMap.toString());
119             }
120 
121             // Find multi-resolution input and output formats
122             int[] multiResolutionInputFormats = multiResolutionMap.getInputFormats();
123             int[] multiResolutionOutputFormats = multiResolutionMap.getOutputFormats();
124 
125             assertTrue("Camera " + id + " must be a logical multi-camera or ultra high res camera "
126                     + "to support multi-resolution reprocessing.",
127                     isLogicalCamera || isUltraHighResCamera);
128 
129             for (int format : multiResolutionInputFormats) {
130                 assertTrue(String.format("Camera %s: multi-resolution input format %d "
131                         + "isn't a supported format", id, format),
132                         CameraTestUtils.contains(inputFormats, format));
133 
134                 Collection<MultiResolutionStreamInfo> multiResolutionStreams =
135                         multiResolutionMap.getInputInfo(format);
136                 assertTrue(String.format("Camera %s supports %d multi-resolution "
137                         + "input stream info, expected at least 2", id,
138                         multiResolutionStreams.size()),
139                         multiResolutionStreams.size() >= 2);
140 
141                 // Make sure that each multi-resolution input stream info has the maximum size
142                 // for that format.
143                 for (MultiResolutionStreamInfo streamInfo : multiResolutionStreams) {
144                     String physicalCameraId = streamInfo.getPhysicalCameraId();
145                     Size streamSize = new Size(streamInfo.getWidth(), streamInfo.getHeight());
146                     if (!isLogicalCamera) {
147                         assertTrue("Camera " + id + " is ultra high resolution camera, but "
148                                 + "the multi-resolution reprocessing stream info camera Id "
149                                 + physicalCameraId + " doesn't match",
150                                 physicalCameraId.equals(id));
151                     } else {
152                         assertTrue("Camera " + id + "'s multi-resolution input info "
153                                 + "physical camera id " + physicalCameraId + " isn't valid",
154                                 physicalCameraIds.contains(physicalCameraId));
155                     }
156 
157                     StaticMetadata pInfo = mAllStaticInfo.get(physicalCameraId);
158                     CameraCharacteristics pChar = pInfo.getCharacteristics();
159                     StreamConfigurationMap pConfig = pChar.get(
160                             CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
161                     Size[] sizes = pConfig.getInputSizes(format);
162 
163                     assertTrue(String.format("Camera %s must "
164                             + "support at least one input size for multi-resolution input "
165                             + "format %d.", physicalCameraId, format),
166                              sizes != null && sizes.length > 0);
167 
168                     List<Size> maxSizes = new ArrayList<Size>();
169                     maxSizes.add(CameraTestUtils.getMaxSize(sizes));
170                     StreamConfigurationMap pMaxResConfig = pChar.get(CameraCharacteristics.
171                             SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION);
172                     if (pMaxResConfig != null) {
173                         Size[] maxResSizes = pMaxResConfig.getInputSizes(format);
174                         if (maxResSizes != null && maxResSizes.length > 0) {
175                             maxSizes.add(CameraTestUtils.getMaxSize(maxResSizes));
176                         }
177                     }
178 
179                     assertTrue(String.format("Camera %s's supported multi-resolution"
180                            + " input size %s for physical camera %s is not one of the largest "
181                            + "supported input sizes %s for format %d", id, streamSize,
182                            physicalCameraId, maxSizes, format), maxSizes.contains(streamSize));
183                 }
184             }
185 
186             // YUV reprocessing capabilities check
187             if (CameraTestUtils.contains(multiResolutionOutputFormats, ImageFormat.YUV_422_888) &&
188                     CameraTestUtils.contains(multiResolutionInputFormats,
189                     ImageFormat.YUV_420_888)) {
190                 assertTrue("The camera device must have YUV_REPROCESSING capability if it "
191                         + "supports multi-resolution YUV input and YUV output",
192                         CameraTestUtils.contains(capabilities,
193                         CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING));
194 
195                 assertTrue("The camera device must supports multi-resolution JPEG output if "
196                         + "supports multi-resolution YUV input and YUV output",
197                         CameraTestUtils.contains(multiResolutionOutputFormats, ImageFormat.JPEG));
198             }
199 
200             // OPAQUE reprocessing capabilities check
201             if (CameraTestUtils.contains(multiResolutionOutputFormats, ImageFormat.PRIVATE) &&
202                     CameraTestUtils.contains(multiResolutionInputFormats, ImageFormat.PRIVATE)) {
203                 assertTrue("The camera device must have PRIVATE_REPROCESSING capability if it "
204                         + "supports multi-resolution PRIVATE input and PRIVATE output",
205                         CameraTestUtils.contains(capabilities,
206                         CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING));
207 
208                 assertTrue("The camera device must supports multi-resolution JPEG output if "
209                         + "supports multi-resolution PRIVATE input and PRIVATE output",
210                         CameraTestUtils.contains(multiResolutionOutputFormats, ImageFormat.JPEG));
211                 assertTrue("The camera device must supports multi-resolution YUV output if "
212                         + "supports multi-resolution PRIVATE input and PRIVATE output",
213                         CameraTestUtils.contains(multiResolutionOutputFormats,
214                         ImageFormat.YUV_420_888));
215             }
216         }
217     }
218 
219     /**
220      * Test YUV_420_888 -> YUV_420_888 multi-resolution reprocessing
221      */
222     @Test
testMultiResolutionYuvToYuvReprocessing()223     public void testMultiResolutionYuvToYuvReprocessing() throws Exception {
224         for (String id : getCameraIdsUnderTest()) {
225             testMultiResolutionReprocessing(id, ImageFormat.YUV_420_888, ImageFormat.YUV_420_888);
226         }
227     }
228 
229     /**
230      * Test YUV_420_888 -> JPEG multi-resolution reprocessing
231      */
232     @Test
testMultiResolutionYuvToJpegReprocessing()233     public void testMultiResolutionYuvToJpegReprocessing() throws Exception {
234         for (String id : getCameraIdsUnderTest()) {
235             testMultiResolutionReprocessing(id, ImageFormat.YUV_420_888, ImageFormat.JPEG);
236         }
237     }
238 
239     /**
240      * Test OPAQUE -> YUV_420_888 multi-resolution reprocessing
241      */
242     @Test
testMultiResolutionOpaqueToYuvReprocessing()243     public void testMultiResolutionOpaqueToYuvReprocessing() throws Exception {
244         for (String id : getCameraIdsUnderTest()) {
245             // Opaque -> YUV_420_888 must be supported.
246             testMultiResolutionReprocessing(id, ImageFormat.PRIVATE, ImageFormat.YUV_420_888);
247         }
248     }
249 
250     /**
251      * Test OPAQUE -> JPEG multi-resolution reprocessing
252      */
253     @Test
testMultiResolutionOpaqueToJpegReprocessing()254     public void testMultiResolutionOpaqueToJpegReprocessing() throws Exception {
255         for (String id : getCameraIdsUnderTest()) {
256             // OPAQUE -> JPEG must be supported.
257             testMultiResolutionReprocessing(id, ImageFormat.PRIVATE, ImageFormat.JPEG);
258         }
259     }
260 
261     /**
262      * Test for making sure the mandatory stream combinations work for multi-resolution
263      * reprocessing.
264      */
265     @Test
testMultiResolutionMandatoryStreamCombinationTest()266     public void testMultiResolutionMandatoryStreamCombinationTest() throws Exception {
267         for (String id : getCameraIdsUnderTest()) {
268             StaticMetadata info = mAllStaticInfo.get(id);
269             CameraCharacteristics c = info.getCharacteristics();
270             MandatoryStreamCombination[] combinations = c.get(
271                             CameraCharacteristics.SCALER_MANDATORY_STREAM_COMBINATIONS);
272             if (combinations == null) {
273                 Log.i(TAG, "No mandatory stream combinations for camera: " + id + " skip test");
274                 continue;
275             }
276             MultiResolutionStreamConfigurationMap multiResolutionMap = c.get(
277                     CameraCharacteristics.SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP);
278             if (multiResolutionMap == null) {
279                 Log.i(TAG, "Camera " + id + " doesn't support multi-resolution capture.");
280                 continue;
281             }
282             int[] multiResolutionInputFormats = multiResolutionMap.getInputFormats();
283             int[] multiResolutionOutputFormats = multiResolutionMap.getOutputFormats();
284             if (multiResolutionInputFormats.length == 0
285                     || multiResolutionOutputFormats.length == 0) {
286                 Log.i(TAG, "Camera " + id + " doesn't support multi-resolution reprocess "
287                         + "input/output.");
288                 continue;
289             }
290 
291             try {
292                 openDevice(id);
293                 for (MandatoryStreamCombination combination : combinations) {
294                     if (!combination.isReprocessable()) {
295                         continue;
296                     }
297 
298                     MandatoryStreamCombination.MandatoryStreamInformation firstStreamInfo =
299                             combination.getStreamsInformation().get(0);
300                     int inputFormat = firstStreamInfo.getFormat();
301                     boolean supportMultiResReprocess = firstStreamInfo.isInput() &&
302                             CameraTestUtils.contains(multiResolutionOutputFormats, inputFormat) &&
303                             CameraTestUtils.contains(multiResolutionInputFormats, inputFormat);
304                     if (!supportMultiResReprocess)  {
305                         continue;
306                     }
307 
308                     testMultiResolutionMandatoryStreamCombination(id, info, combination,
309                             multiResolutionMap);
310                 }
311             } finally {
312                 closeDevice(id);
313             }
314         }
315     }
316 
testMultiResolutionMandatoryStreamCombination(String cameraId, StaticMetadata staticInfo, MandatoryStreamCombination combination, MultiResolutionStreamConfigurationMap multiResStreamConfig)317     private void testMultiResolutionMandatoryStreamCombination(String cameraId,
318             StaticMetadata staticInfo, MandatoryStreamCombination combination,
319             MultiResolutionStreamConfigurationMap multiResStreamConfig) throws Exception {
320         String log = "Testing multi-resolution mandatory stream combination: " +
321                 combination.getDescription() + " on camera: " + cameraId;
322         Log.i(TAG, log);
323 
324         final int TIMEOUT_FOR_RESULT_MS = 5000;
325         final int NUM_REPROCESS_CAPTURES_PER_CONFIG = 3;
326 
327         // Set up outputs
328         List<OutputConfiguration> outputConfigs = new ArrayList<OutputConfiguration>();
329         List<Surface> outputSurfaces = new ArrayList<Surface>();
330         StreamCombinationTargets targets = new StreamCombinationTargets();
331         MultiResolutionImageReader inputReader = null;
332         ImageWriter inputWriter = null;
333         SimpleImageReaderListener inputReaderListener = new SimpleImageReaderListener();
334         SimpleCaptureCallback inputCaptureListener = new SimpleCaptureCallback();
335         SimpleCaptureCallback reprocessOutputCaptureListener = new SimpleCaptureCallback();
336 
337         List<MandatoryStreamInformation> streamInfo = combination.getStreamsInformation();
338         assertTrue("Reprocessable stream combinations should have at least 3 or more streams",
339                     (streamInfo != null) && (streamInfo.size() >= 3));
340         assertTrue("The first mandatory stream information in a reprocessable combination must " +
341                 "always be input", streamInfo.get(0).isInput());
342 
343         int inputFormat = streamInfo.get(0).getFormat();
344 
345         CameraTestUtils.setupConfigurationTargets(streamInfo.subList(2, streamInfo.size()),
346                 targets, outputConfigs, outputSurfaces, NUM_REPROCESS_CAPTURES_PER_CONFIG,
347                 /*substituteY8*/false, /*substituteHeic*/false, /*physicalCameraId*/null,
348                 multiResStreamConfig, mHandler);
349 
350         Collection<MultiResolutionStreamInfo> multiResInputs =
351                 multiResStreamConfig.getInputInfo(inputFormat);
352         InputConfiguration inputConfig = new InputConfiguration(multiResInputs, inputFormat);
353 
354         try {
355             // For each config, YUV and JPEG outputs will be tested. (For YUV reprocessing,
356             // the YUV ImageReader for input is also used for output.)
357             final boolean inputIsYuv = inputConfig.getFormat() == ImageFormat.YUV_420_888;
358             final boolean useYuv = inputIsYuv || targets.mYuvTargets.size() > 0 ||
359                     targets.mYuvMultiResTargets.size() > 0;
360             final int totalNumReprocessCaptures =  NUM_REPROCESS_CAPTURES_PER_CONFIG * (
361                     (inputIsYuv ? 1 : 0) + targets.mJpegMultiResTargets.size() +
362                     targets.mJpegTargets.size() +
363                     (useYuv ? targets.mYuvMultiResTargets.size() + targets.mYuvTargets.size() : 0));
364 
365             // It needs 1 input buffer for each reprocess capture + the number of buffers
366             // that will be used as outputs.
367             inputReader = new MultiResolutionImageReader(multiResInputs, inputFormat,
368                     totalNumReprocessCaptures + NUM_REPROCESS_CAPTURES_PER_CONFIG);
369             inputReader.setOnImageAvailableListener(
370                     inputReaderListener, new HandlerExecutor(mHandler));
371             outputConfigs.addAll(
372                     OutputConfiguration.createInstancesForMultiResolutionOutput(inputReader));
373             outputSurfaces.add(inputReader.getSurface());
374 
375             CameraCaptureSession.CaptureCallback mockCaptureCallback =
376                     mock(CameraCaptureSession.CaptureCallback.class);
377 
378             checkSessionConfigurationSupported(mCamera, mHandler, outputConfigs,
379                     inputConfig, SessionConfiguration.SESSION_REGULAR,
380                     mCameraManager, true/*defaultSupport*/, String.format(
381                     "Session configuration query for multi-res combination: %s failed",
382                     combination.getDescription()));
383 
384             // Verify we can create a reprocessable session with the input and all outputs.
385             BlockingSessionCallback sessionListener = new BlockingSessionCallback();
386             CameraCaptureSession session = configureReprocessableCameraSessionWithConfigurations(
387                     mCamera, inputConfig, outputConfigs, sessionListener, mHandler);
388             inputWriter = ImageWriter.newInstance(
389                     session.getInputSurface(), totalNumReprocessCaptures);
390 
391             // Prepare a request for reprocess input
392             CaptureRequest.Builder builder =
393                     mCamera.createCaptureRequest(CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG);
394             builder.addTarget(inputReader.getSurface());
395 
396             for (int i = 0; i < totalNumReprocessCaptures; i++) {
397                 session.capture(builder.build(), inputCaptureListener, mHandler);
398             }
399 
400             List<CaptureRequest> reprocessRequests = new ArrayList<>();
401             List<Surface> reprocessOutputs = new ArrayList<>();
402 
403             if (inputIsYuv) {
404                 reprocessOutputs.add(inputReader.getSurface());
405             }
406             for (MultiResolutionImageReader reader : targets.mJpegMultiResTargets) {
407                 reprocessOutputs.add(reader.getSurface());
408             }
409             for (ImageReader reader : targets.mJpegTargets) {
410                 reprocessOutputs.add(reader.getSurface());
411             }
412             for (MultiResolutionImageReader reader : targets.mYuvMultiResTargets) {
413                 reprocessOutputs.add(reader.getSurface());
414             }
415             for (ImageReader reader : targets.mYuvTargets) {
416                 reprocessOutputs.add(reader.getSurface());
417             }
418 
419             for (int i = 0; i < NUM_REPROCESS_CAPTURES_PER_CONFIG; i++) {
420                 for (Surface output : reprocessOutputs) {
421                     TotalCaptureResult result = inputCaptureListener.getTotalCaptureResult(
422                             TIMEOUT_FOR_RESULT_MS);
423                     Map<String, TotalCaptureResult> physicalResults =
424                             result.getPhysicalCameraTotalResults();
425                     for (Map.Entry<String, TotalCaptureResult> entry : physicalResults.entrySet()) {
426                         String physicalCameraId = entry.getKey();
427                         TotalCaptureResult physicalResult = entry.getValue();
428                         String activePhysicalId = physicalResult.get(
429                                 CaptureResult.LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID);
430                         mCollector.expectEquals(String.format(
431                                 "Physical camera result metadata must contain activePhysicalId " +
432                                 "(%s) matching with physical camera Id (%s).", activePhysicalId,
433                                 physicalCameraId), physicalCameraId, activePhysicalId);
434                     }
435 
436                     String activePhysicalCameraId = result.get(
437                             CaptureResult.LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID);
438                     if (activePhysicalCameraId != null) {
439                         result = physicalResults.get(activePhysicalCameraId);
440                     }
441 
442                     builder = mCamera.createReprocessCaptureRequest(result);
443                     inputWriter.queueInputImage(
444                             inputReaderListener.getImage(TIMEOUT_FOR_RESULT_MS));
445                     builder.addTarget(output);
446                     reprocessRequests.add(builder.build());
447                 }
448             }
449 
450             session.captureBurst(reprocessRequests, reprocessOutputCaptureListener, mHandler);
451 
452             for (int i = 0; i < reprocessOutputs.size() * NUM_REPROCESS_CAPTURES_PER_CONFIG; i++) {
453                 TotalCaptureResult result = reprocessOutputCaptureListener.getTotalCaptureResult(
454                         TIMEOUT_FOR_RESULT_MS);
455             }
456         } catch (Throwable e) {
457             mCollector.addMessage(
458                     String.format("Mandatory multi-res stream combination: %s failed due: %s",
459                     combination.getDescription(), e.getMessage()));
460         } finally {
461             inputReaderListener.drain();
462             reprocessOutputCaptureListener.drain();
463             targets.close();
464 
465             if (inputReader != null) {
466                 inputReader.close();
467             }
468 
469             if (inputWriter != null) {
470                 inputWriter.close();
471             }
472         }
473     }
474 
475     /**
476      * Test multi-resolution reprocessing from the input format to the output format
477      */
testMultiResolutionReprocessing(String cameraId, int inputFormat, int outputFormat)478     private void testMultiResolutionReprocessing(String cameraId, int inputFormat,
479             int outputFormat) throws Exception {
480         if (VERBOSE) {
481             Log.v(TAG, "testMultiResolutionReprocessing: cameraId: " + cameraId + " inputFormat: "
482                     + inputFormat + " outputFormat: " + outputFormat);
483         }
484 
485         Collection<MultiResolutionStreamInfo> inputStreamInfo =
486                 getMultiResReprocessInfo(cameraId, inputFormat, /*input*/ true);
487         Collection<MultiResolutionStreamInfo> regularOutputStreamInfo =
488                 getMultiResReprocessInfo(cameraId, inputFormat, /*input*/ false);
489         Collection<MultiResolutionStreamInfo> reprocessOutputStreamInfo =
490                 getMultiResReprocessInfo(cameraId, outputFormat, /*input*/ false);
491         if (inputStreamInfo == null || regularOutputStreamInfo == null ||
492                 reprocessOutputStreamInfo == null) {
493             return;
494         }
495         assertTrue("The multi-resolution stream info for format " + inputFormat
496                 + " must be equal between input and output",
497                 inputStreamInfo.containsAll(regularOutputStreamInfo)
498                 && regularOutputStreamInfo.containsAll(inputStreamInfo));
499 
500         try {
501             openDevice(cameraId);
502 
503             testMultiResolutionReprocessWithStreamInfo(cameraId, inputFormat, inputStreamInfo,
504                     outputFormat, reprocessOutputStreamInfo);
505         } finally {
506             closeDevice(cameraId);
507         }
508     }
509 
510     /**
511      * Test multi-resolution reprocess with multi-resolution stream info lists for a particular
512      * format combination.
513      */
testMultiResolutionReprocessWithStreamInfo(String cameraId, int inputFormat, Collection<MultiResolutionStreamInfo> inputInfo, int outputFormat, Collection<MultiResolutionStreamInfo> outputInfo)514     private void testMultiResolutionReprocessWithStreamInfo(String cameraId,
515             int inputFormat, Collection<MultiResolutionStreamInfo> inputInfo,
516             int outputFormat, Collection<MultiResolutionStreamInfo> outputInfo)
517             throws Exception {
518         try {
519             setupMultiResImageReaders(inputFormat, inputInfo, outputFormat, outputInfo,
520                     /*maxImages*/1);
521             setupReprocessableSession(inputFormat, inputInfo, outputInfo,
522                     /*numImageWriterImages*/1);
523 
524             List<Float> zoomRatioList = CameraTestUtils.getCandidateZoomRatios(mStaticInfo);
525             for (Float zoomRatio :  zoomRatioList) {
526                 ImageResultSizeHolder imageResultSizeHolder = null;
527 
528                 try {
529                     imageResultSizeHolder = doMultiResReprocessCapture(zoomRatio);
530                     Image reprocessedImage = imageResultSizeHolder.getImage();
531                     Size outputSize = imageResultSizeHolder.getExpectedSize();
532                     TotalCaptureResult result = imageResultSizeHolder.getTotalCaptureResult();
533 
534                     mCollector.expectImageProperties("testMultiResolutionReprocess",
535                             reprocessedImage, outputFormat, outputSize,
536                             result.get(CaptureResult.SENSOR_TIMESTAMP));
537 
538                     if (DEBUG) {
539                         Log.d(TAG, String.format("camera %s %d zoom %f out %dx%d %d",
540                                 cameraId, inputFormat, zoomRatio,
541                                 outputSize.getWidth(), outputSize.getHeight(),
542                                 outputFormat));
543 
544                         dumpImage(reprocessedImage,
545                                 "/testMultiResolutionReprocess_camera" + cameraId
546                                 + "_" + mDumpFrameCount);
547                         mDumpFrameCount++;
548                     }
549                 } finally {
550                     if (imageResultSizeHolder != null) {
551                         imageResultSizeHolder.getImage().close();
552                     }
553                 }
554             }
555         } finally {
556             closeReprossibleSession();
557             closeMultiResImageReaders();
558         }
559     }
560 
561     /**
562      * Set up multi-resolution image readers for regular and reprocess output
563      *
564      * <p>If the reprocess input format is equal to output format, share one multi-resolution
565      * image reader.</p>
566      */
setupMultiResImageReaders(int inputFormat, Collection<MultiResolutionStreamInfo> inputInfo, int outputFormat, Collection<MultiResolutionStreamInfo> outputInfo, int maxImages)567     private void setupMultiResImageReaders(int inputFormat,
568             Collection<MultiResolutionStreamInfo> inputInfo, int outputFormat,
569             Collection<MultiResolutionStreamInfo> outputInfo, int maxImages) {
570 
571         mShareOneReader = false;
572         // If the regular output and reprocess output have the same format,
573         // they can share one MultiResolutionImageReader.
574         if (inputFormat == outputFormat) {
575             maxImages *= 2;
576             mShareOneReader = true;
577         }
578 
579         // create an MultiResolutionImageReader for the regular capture
580         mMultiResImageReader = new MultiResolutionImageReader(inputInfo,
581                 inputFormat, maxImages);
582         mMultiResImageReaderListener = new SimpleMultiResolutionImageReaderListener(
583                 mMultiResImageReader, 1, /*repeating*/false);
584         mMultiResImageReader.setOnImageAvailableListener(mMultiResImageReaderListener,
585                 new HandlerExecutor(mHandler));
586 
587         if (!mShareOneReader) {
588             // create an MultiResolutionImageReader for the reprocess capture
589             mSecondMultiResImageReader = new MultiResolutionImageReader(
590                     outputInfo, outputFormat, maxImages);
591             mSecondMultiResImageReaderListener = new SimpleMultiResolutionImageReaderListener(
592                     mSecondMultiResImageReader, maxImages, /*repeating*/ false);
593             mSecondMultiResImageReader.setOnImageAvailableListener(
594                     mSecondMultiResImageReaderListener, new HandlerExecutor(mHandler));
595         }
596     }
597 
598     /**
599      * Close two multi-resolution image readers.
600      */
closeMultiResImageReaders()601     private void closeMultiResImageReaders() {
602         mMultiResImageReader.close();
603         mMultiResImageReader = null;
604 
605         if (!mShareOneReader) {
606             mSecondMultiResImageReader.close();
607             mSecondMultiResImageReader = null;
608         }
609     }
610 
611     /**
612      * Get the MultiResolutionImageReader for reprocess output.
613      */
getOutputMultiResImageReader()614     private MultiResolutionImageReader getOutputMultiResImageReader() {
615         if (mShareOneReader) {
616             return mMultiResImageReader;
617         } else {
618             return mSecondMultiResImageReader;
619         }
620     }
621 
622     /**
623      * Get the MultiResolutionImageReaderListener for reprocess output.
624      */
getOutputMultiResImageReaderListener()625     private SimpleMultiResolutionImageReaderListener getOutputMultiResImageReaderListener() {
626         if (mShareOneReader) {
627             return mMultiResImageReaderListener;
628         } else {
629             return mSecondMultiResImageReaderListener;
630         }
631     }
632 
633     /**
634      * Set up a reprocessable session and create an ImageWriter with the session's input surface.
635      */
setupReprocessableSession(int inputFormat, Collection<MultiResolutionStreamInfo> inputInfo, Collection<MultiResolutionStreamInfo> outputInfo, int numImageWriterImages)636     private void setupReprocessableSession(int inputFormat,
637             Collection<MultiResolutionStreamInfo> inputInfo,
638             Collection<MultiResolutionStreamInfo> outputInfo,
639             int numImageWriterImages) throws Exception {
640         // create a reprocessable capture session
641         Collection<OutputConfiguration> outConfigs =
642                 OutputConfiguration.createInstancesForMultiResolutionOutput(
643                         mMultiResImageReader);
644         ArrayList<OutputConfiguration> outputConfigsList = new ArrayList<OutputConfiguration>(
645                 outConfigs);
646 
647         if (!mShareOneReader) {
648             Collection<OutputConfiguration> secondOutputConfigs =
649                     OutputConfiguration.createInstancesForMultiResolutionOutput(
650                             mSecondMultiResImageReader);
651             outputConfigsList.addAll(secondOutputConfigs);
652         }
653 
654         InputConfiguration inputConfig = new InputConfiguration(inputInfo, inputFormat);
655         if (VERBOSE) {
656             String inputConfigString = inputConfig.toString();
657             Log.v(TAG, "InputConfiguration: " + inputConfigString);
658         }
659 
660         mCameraSessionListener = new BlockingSessionCallback();
661         mCameraSession = configureReprocessableCameraSessionWithConfigurations(
662                 mCamera, inputConfig, outputConfigsList, mCameraSessionListener, mHandler);
663 
664         // create an ImageWriter
665         mInputSurface = mCameraSession.getInputSurface();
666         mImageWriter = ImageWriter.newInstance(mInputSurface,
667                 numImageWriterImages);
668 
669         mImageWriterListener = new SimpleImageWriterListener(mImageWriter);
670         mImageWriter.setOnImageReleasedListener(mImageWriterListener, mHandler);
671     }
672 
673     /**
674      * Close the reprocessable session and ImageWriter.
675      */
closeReprossibleSession()676     private void closeReprossibleSession() {
677         mInputSurface = null;
678 
679         if (mCameraSession != null) {
680             mCameraSession.close();
681             mCameraSession = null;
682         }
683 
684         if (mImageWriter != null) {
685             mImageWriter.close();
686             mImageWriter = null;
687         }
688     }
689 
690     /**
691      * Do one multi-resolution reprocess capture for the specified zoom ratio
692      */
doMultiResReprocessCapture(float zoomRatio)693     private ImageResultSizeHolder doMultiResReprocessCapture(float zoomRatio) throws Exception {
694         // submit a regular capture and get the result
695         TotalCaptureResult totalResult = submitCaptureRequest(
696                 zoomRatio, mMultiResImageReader.getSurface(), /*inputResult*/null);
697         Map<String, TotalCaptureResult> physicalResults =
698                 totalResult.getPhysicalCameraTotalResults();
699 
700         ImageAndMultiResStreamInfo inputImageAndInfo =
701                 mMultiResImageReaderListener.getAnyImageAndInfoAvailable(CAPTURE_TIMEOUT_MS);
702         assertNotNull("Failed to capture input image", inputImageAndInfo);
703         Image inputImage = inputImageAndInfo.image;
704         MultiResolutionStreamInfo inputStreamInfo = inputImageAndInfo.streamInfo;
705         TotalCaptureResult inputSettings =
706                 physicalResults.get(inputStreamInfo.getPhysicalCameraId());
707         assertTrue("Regular capture's TotalCaptureResult doesn't contain capture result for "
708                 + "physical camera id " + inputStreamInfo.getPhysicalCameraId(),
709                 inputSettings != null);
710 
711         // Submit a reprocess capture and get the result
712         mImageWriter.queueInputImage(inputImage);
713 
714         TotalCaptureResult finalResult = submitCaptureRequest(zoomRatio,
715                 getOutputMultiResImageReader().getSurface(), inputSettings);
716 
717         ImageAndMultiResStreamInfo outputImageAndInfo =
718                 getOutputMultiResImageReaderListener().getAnyImageAndInfoAvailable(
719                 CAPTURE_TIMEOUT_MS);
720         Image outputImage = outputImageAndInfo.image;
721         MultiResolutionStreamInfo outputStreamInfo = outputImageAndInfo.streamInfo;
722 
723         assertTrue("The regular output and reprocess output's stream info must be the same",
724                 outputStreamInfo.equals(inputStreamInfo));
725 
726         ImageResultSizeHolder holder = new ImageResultSizeHolder(outputImageAndInfo.image,
727                 finalResult, new Size(outputStreamInfo.getWidth(), outputStreamInfo.getHeight()));
728 
729         return holder;
730     }
731 
732     /**
733      * Issue a capture request and return the result for a particular zoom ratio.
734      *
735      * <p>If inputResult is null, it's a regular request. Otherwise, it's a reprocess request.</p>
736      */
submitCaptureRequest(float zoomRatio, Surface output, TotalCaptureResult inputResult)737     private TotalCaptureResult submitCaptureRequest(float zoomRatio,
738             Surface output, TotalCaptureResult inputResult) throws Exception {
739 
740         SimpleCaptureCallback captureCallback = new SimpleCaptureCallback();
741 
742         // Prepare a list of capture requests. Whether it's a regular or reprocess capture request
743         // is based on inputResult.
744         CaptureRequest.Builder builder;
745         boolean isReprocess = (inputResult != null);
746         if (isReprocess) {
747             builder = mCamera.createReprocessCaptureRequest(inputResult);
748         } else {
749             builder = mCamera.createCaptureRequest(CAPTURE_TEMPLATE);
750             builder.set(CaptureRequest.CONTROL_ZOOM_RATIO, zoomRatio);
751         }
752         builder.addTarget(output);
753         CaptureRequest request = builder.build();
754         assertTrue("Capture request reprocess type " + request.isReprocess() + " is wrong.",
755             request.isReprocess() == isReprocess);
756 
757         mCameraSession.capture(request, captureCallback, mHandler);
758 
759         TotalCaptureResult result = captureCallback.getTotalCaptureResultForRequest(
760                 request, CAPTURE_TIMEOUT_FRAMES);
761 
762         // make sure all input surfaces are released.
763         if (isReprocess) {
764             mImageWriterListener.waitForImageReleased(CAPTURE_TIMEOUT_MS);
765         }
766 
767         return result;
768     }
769 
getMaxSize(int format, StaticMetadata.StreamDirection direction)770     private Size getMaxSize(int format, StaticMetadata.StreamDirection direction) {
771         Size[] sizes = mStaticInfo.getAvailableSizesForFormatChecked(format, direction);
772         return getAscendingOrderSizes(Arrays.asList(sizes), /*ascending*/false).get(0);
773     }
774 
getMultiResReprocessInfo(String cameraId, int format, boolean input)775     private Collection<MultiResolutionStreamInfo> getMultiResReprocessInfo(String cameraId,
776             int format, boolean input) throws Exception {
777         StaticMetadata staticInfo = mAllStaticInfo.get(cameraId);
778         CameraCharacteristics characteristics = staticInfo.getCharacteristics();
779         MultiResolutionStreamConfigurationMap configs = characteristics.get(
780                 CameraCharacteristics.SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP);
781         if (configs == null) {
782             Log.i(TAG, "Camera " + cameraId + " doesn't support multi-resolution streams");
783             return null;
784         }
785 
786         String streamType = input ? "input" : "output";
787         int[] formats = input ? configs.getInputFormats() :
788                 configs.getOutputFormats();
789         if (!CameraTestUtils.contains(formats, format)) {
790             Log.i(TAG, "Camera " + cameraId + " doesn't support multi-resolution "
791                     + streamType + " stream for format " + format + ". Supported formats are "
792                     + Arrays.toString(formats));
793             return null;
794         }
795         Collection<MultiResolutionStreamInfo> streams =
796                 input ? configs.getInputInfo(format) : configs.getOutputInfo(format);
797         mCollector.expectTrue(String.format("Camera %s supported 0 multi-resolution "
798                 + streamType + " stream info, expected at least 1", cameraId),
799                 streams.size() > 0);
800 
801         return streams;
802     }
803 
dumpImage(Image image, String name)804     private void dumpImage(Image image, String name) {
805         String filename = mDebugFileNameBase + name;
806         switch(image.getFormat()) {
807             case ImageFormat.JPEG:
808                 filename += ".jpg";
809                 break;
810             case ImageFormat.YUV_420_888:
811                 filename += ".yuv";
812                 break;
813             default:
814                 filename += "." + image.getFormat();
815                 break;
816         }
817 
818         Log.d(TAG, "dumping an image to " + filename);
819         dumpFile(filename , getDataFromImage(image));
820     }
821 
822     /**
823      * A class that holds an Image, a TotalCaptureResult, and expected image size.
824      */
825     public static class ImageResultSizeHolder {
826         private final Image mImage;
827         private final TotalCaptureResult mResult;
828         private final Size mExpectedSize;
829 
ImageResultSizeHolder(Image image, TotalCaptureResult result, Size expectedSize)830         public ImageResultSizeHolder(Image image, TotalCaptureResult result, Size expectedSize) {
831             mImage = image;
832             mResult = result;
833             mExpectedSize = expectedSize;
834         }
835 
getImage()836         public Image getImage() {
837             return mImage;
838         }
839 
getTotalCaptureResult()840         public TotalCaptureResult getTotalCaptureResult() {
841             return mResult;
842         }
843 
getExpectedSize()844         public Size getExpectedSize() {
845             return mExpectedSize;
846         }
847     }
848 
849 }
850