1 /* 2 * Copyright 2020 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 import static android.hardware.camera2.cts.CameraTestUtils.MaxStreamSizes.*; 21 22 import android.graphics.ImageFormat; 23 import android.graphics.SurfaceTexture; 24 import android.hardware.camera2.CameraCaptureSession; 25 import android.hardware.camera2.CameraDevice; 26 import android.hardware.camera2.CaptureRequest; 27 import android.hardware.camera2.CaptureResult; 28 import android.hardware.camera2.TotalCaptureResult; 29 import android.hardware.camera2.CaptureFailure; 30 import android.hardware.camera2.cts.helpers.StaticMetadata; 31 import android.hardware.camera2.cts.testcases.Camera2ConcurrentAndroidTestCase; 32 import android.hardware.camera2.params.OutputConfiguration; 33 import android.hardware.camera2.params.MandatoryStreamCombination; 34 import android.hardware.camera2.params.MandatoryStreamCombination.MandatoryStreamInformation; 35 import android.hardware.camera2.params.SessionConfiguration; 36 import android.media.ImageReader; 37 import android.util.Log; 38 import android.util.Pair; 39 import android.view.Surface; 40 41 import com.android.ex.camera2.blocking.BlockingSessionCallback; 42 43 import java.util.Arrays; 44 import java.util.ArrayList; 45 import java.util.Comparator; 46 import java.util.List; 47 import java.util.Map; 48 import java.util.HashMap; 49 import java.util.Set; 50 51 import org.junit.runners.Parameterized; 52 import org.junit.runner.RunWith; 53 import org.junit.Test; 54 55 import static junit.framework.Assert.assertTrue; 56 import static org.mockito.Mockito.*; 57 58 /** 59 * Tests exercising concurrent camera streaming mandatory stream combinations. 60 */ 61 62 @RunWith(Parameterized.class) 63 public class ConcurrentCameraTest extends Camera2ConcurrentAndroidTestCase { 64 private static final String TAG = "ConcurrentCameraTest"; 65 private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); 66 67 /** 68 * Class representing a per camera id test sample. 69 */ 70 private static class TestSample { 71 public String cameraId; 72 public StaticMetadata staticInfo; 73 public MandatoryStreamCombination combination; 74 public boolean haveSession = false; 75 public boolean substituteY8; 76 public List<OutputConfiguration> outputConfigs = new ArrayList<OutputConfiguration>(); 77 public List<Surface> outputSurfaces = new ArrayList<Surface>(); 78 public StreamCombinationTargets targets = new StreamCombinationTargets(); TestSample(String cameraId, StaticMetadata staticInfo, MandatoryStreamCombination combination, boolean subY8)79 public TestSample(String cameraId, StaticMetadata staticInfo, 80 MandatoryStreamCombination combination, boolean subY8) { 81 this.cameraId = cameraId; 82 this.staticInfo = staticInfo; 83 this.combination = combination; 84 this.substituteY8 = subY8; 85 } 86 } 87 88 /** 89 * Class representing the information needed to generate a unique combination per camera id 90 */ 91 private static class GeneratedEntry { 92 public String cameraId; 93 public boolean substituteY8; 94 MandatoryStreamCombination combination; 95 //Note: We don't have physical camera ids here since physical camera ids do not have 96 // guaranteed concurrent stream combinations. GeneratedEntry(String cameraId, boolean substituteY8, MandatoryStreamCombination combination)97 GeneratedEntry(String cameraId, boolean substituteY8, 98 MandatoryStreamCombination combination) { 99 this.cameraId = cameraId; 100 this.substituteY8 = substituteY8; 101 this.combination = combination; 102 } 103 }; 104 105 @Test testMandatoryConcurrentStreamCombination()106 public void testMandatoryConcurrentStreamCombination() throws Exception { 107 for (Set<String> cameraIdCombinations : mConcurrentCameraIdCombinations) { 108 if (cameraIdCombinations.size() == 0) { 109 continue; 110 } 111 List<HashMap<String, GeneratedEntry>> streamCombinationPermutations = 112 generateStreamSelections(cameraIdCombinations); 113 114 for (HashMap<String, GeneratedEntry> streamCombinationPermutation : 115 streamCombinationPermutations) { 116 ArrayList<TestSample> testSamples = new ArrayList<TestSample>(); 117 for (Map.Entry<String, GeneratedEntry> deviceSample : 118 streamCombinationPermutation.entrySet()) { 119 CameraTestInfo info = mCameraTestInfos.get(deviceSample.getKey()); 120 assertTrue("CameraTestInfo not found for camera id " + deviceSample.getKey(), 121 info != null); 122 MandatoryStreamCombination chosenCombination = 123 deviceSample.getValue().combination; 124 boolean substituteY8 = deviceSample.getValue().substituteY8; 125 TestSample testSample = new TestSample(deviceSample.getKey(), info.mStaticInfo, 126 chosenCombination, substituteY8); 127 testSamples.add(testSample); 128 openDevice(deviceSample.getKey()); 129 } 130 try { 131 testMandatoryConcurrentStreamCombination(testSamples); 132 } finally { 133 for (TestSample testSample : testSamples) { 134 closeDevice(testSample.cameraId); 135 } 136 } 137 } 138 } 139 } 140 141 // @return Generates a List of HashMaps<String, GeneratedEntry> which is a map: 142 // cameraId -> GeneratedEntry (represents a camera device's test configuration) 143 // @param perIdGeneratedEntries a per-camera id set of combinations to recursively generate 144 // GeneratedEntries for. generateStreamSelectionsInternal( HashMap<String, List<GeneratedEntry>> perIdGeneratedEntries)145 private List<HashMap<String, GeneratedEntry>> generateStreamSelectionsInternal( 146 HashMap<String, List<GeneratedEntry>> perIdGeneratedEntries) { 147 Set<String> cameraIds = perIdGeneratedEntries.keySet(); 148 // Base condition for recursion. 149 if (cameraIds.size() == 1) { 150 String cameraId = cameraIds.toArray(new String[1])[0]; 151 List<HashMap<String, GeneratedEntry>> ret = 152 new ArrayList<HashMap<String, GeneratedEntry>> (); 153 for (GeneratedEntry entry : perIdGeneratedEntries.get(cameraId)) { 154 HashMap<String, GeneratedEntry> retPut = new HashMap<String, GeneratedEntry>(); 155 retPut.put(cameraId, entry); 156 ret.add(retPut); 157 } 158 return ret; 159 } 160 // Choose one camera id, create all combinations for the remaining set and then add each of 161 // the camera id's combinations to the returned list of maps. 162 String keyChosen = null; 163 for (String cameraId: cameraIds) { 164 keyChosen = cameraId; 165 break; 166 } 167 List<GeneratedEntry> selfGeneratedEntries = perIdGeneratedEntries.get(keyChosen); 168 perIdGeneratedEntries.remove(keyChosen); 169 List<HashMap<String, GeneratedEntry>> recResult = 170 generateStreamSelectionsInternal(perIdGeneratedEntries); 171 List<HashMap<String, GeneratedEntry>> res = 172 new ArrayList<HashMap<String, GeneratedEntry>>(); 173 for (GeneratedEntry gen : selfGeneratedEntries) { 174 for (HashMap<String, GeneratedEntry> entryMap : recResult) { 175 // Make a copy of the HashMap, add the generated entry to it and add it to the final 176 // result (since we want to use the original for the other 'gen' entries) 177 HashMap<String, GeneratedEntry> copy = (HashMap)entryMap.clone(); 178 copy.put(keyChosen, gen); 179 res.add(copy); 180 } 181 } 182 return res; 183 } 184 185 /** 186 * Generates a list of combinations used for mandatory stream combination testing. 187 * Each combination(GeneratedEntry) corresponds to a camera id advertised by 188 * getConcurrentCameraIds(). 189 */ generateStreamSelections( Set<String> cameraIdCombination)190 private List<HashMap<String, GeneratedEntry>> generateStreamSelections( 191 Set<String> cameraIdCombination) { 192 // First artificially create lists of GeneratedEntries for each camera id in the passed 193 // set. 194 HashMap<String, List<GeneratedEntry>> perIdGeneratedEntries = 195 new HashMap<String, List<GeneratedEntry>>(); 196 for (String cameraId : cameraIdCombination) { 197 List<GeneratedEntry> genEntries = getGeneratedEntriesFor(cameraId); 198 perIdGeneratedEntries.put(cameraId, genEntries); 199 } 200 return generateStreamSelectionsInternal(perIdGeneratedEntries); 201 } 202 203 // get GeneratedEntries for a particular camera id. getGeneratedEntriesFor(String cameraId)204 List<GeneratedEntry> getGeneratedEntriesFor(String cameraId) { 205 CameraTestInfo info = mCameraTestInfos.get(cameraId); 206 assertTrue("CameraTestInfo not found for camera id " + cameraId, info != null); 207 MandatoryStreamCombination[] combinations = info.mMandatoryStreamCombinations; 208 List<GeneratedEntry> generatedEntries = new ArrayList<GeneratedEntry>(); 209 210 // Now generate entries on the camera's mandatory streams 211 for (MandatoryStreamCombination combination : combinations) { 212 generatedEntries.add(new GeneratedEntry(cameraId,/*substituteY8*/false, combination)); 213 // Check whether substituting YUV_888 format with Y8 format 214 boolean substituteY8 = false; 215 if (info.mStaticInfo.isMonochromeWithY8()) { 216 List<MandatoryStreamInformation> streamsInfo = combination.getStreamsInformation(); 217 for (MandatoryStreamInformation streamInfo : streamsInfo) { 218 if (streamInfo.getFormat() == ImageFormat.YUV_420_888) { 219 substituteY8 = true; 220 break; 221 } 222 } 223 } 224 225 if (substituteY8) { 226 generatedEntries.add(new GeneratedEntry(cameraId, /*substituteY8*/true, 227 combination)); 228 } 229 } 230 return generatedEntries; 231 } 232 testMandatoryConcurrentStreamCombination(ArrayList<TestSample> testSamples)233 private void testMandatoryConcurrentStreamCombination(ArrayList<TestSample> testSamples) 234 throws Exception { 235 236 final int TIMEOUT_FOR_RESULT_MS = 1000; 237 final int MIN_RESULT_COUNT = 3; 238 HashMap<String, SessionConfiguration> testSessionMap = 239 new HashMap<String, SessionConfiguration>(); 240 for (TestSample testSample : testSamples) { 241 CameraTestInfo info = mCameraTestInfos.get(testSample.cameraId); 242 assertTrue("CameraTestInfo not found for camera id " + testSample.cameraId, 243 info != null); 244 List<OutputConfiguration> outputConfigs = new ArrayList<>(); 245 CameraTestUtils.setupConfigurationTargets( 246 testSample.combination.getStreamsInformation(), testSample.targets, 247 outputConfigs, testSample.outputSurfaces, MIN_RESULT_COUNT, 248 testSample.substituteY8, /*substituteHEIC*/false, /*physicalCameraId*/null, 249 /*multiResStreamConfig*/null, mHandler); 250 for (OutputConfiguration c : outputConfigs) { 251 testSample.outputConfigs.add(c); 252 } 253 254 try { 255 checkSessionConfigurationSupported(info.mCamera, mHandler, testSample.outputConfigs, 256 /*inputConfig*/ null, SessionConfiguration.SESSION_REGULAR, 257 mCameraManager, true/*defaultSupport*/, String.format( 258 "Session configuration query from combination: %s failed", 259 testSample.combination.getDescription())); 260 testSessionMap.put(testSample.cameraId, new SessionConfiguration( 261 SessionConfiguration.SESSION_REGULAR,testSample.outputConfigs, 262 new HandlerExecutor(mHandler), new BlockingSessionCallback())); 263 } catch (Throwable e) { 264 mCollector.addMessage(String.format( 265 "Mandatory stream combination: %s for camera id %s failed due: %s", 266 testSample.combination.getDescription(), testSample.cameraId, 267 e.getMessage())); 268 } 269 } 270 271 // Mandatory stream combinations must be reported as supported. 272 assertTrue("Concurrent session configs not supported", 273 mCameraManager.isConcurrentSessionConfigurationSupported(testSessionMap)); 274 275 for (TestSample testSample : testSamples) { 276 try { 277 CameraTestInfo info = mCameraTestInfos.get(testSample.cameraId); 278 assertTrue("CameraTestInfo not found for camera id " + testSample.cameraId, 279 info != null); 280 createSessionByConfigs(testSample.cameraId, testSample.outputConfigs); 281 testSample.haveSession = true; 282 CaptureRequest.Builder requestBuilder = 283 info.mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); 284 285 for (OutputConfiguration c : testSample.outputConfigs) { 286 requestBuilder.addTarget(c.getSurface()); 287 } 288 CaptureRequest request = requestBuilder.build(); 289 CameraCaptureSession.CaptureCallback mockCaptureCallback = 290 mock(CameraCaptureSession.CaptureCallback.class); 291 info.mCameraSession.setRepeatingRequest(request, mockCaptureCallback, mHandler); 292 293 verify(mockCaptureCallback, 294 timeout(TIMEOUT_FOR_RESULT_MS * MIN_RESULT_COUNT).atLeast(MIN_RESULT_COUNT)) 295 .onCaptureCompleted( 296 eq(info.mCameraSession), 297 eq(request), 298 isA(TotalCaptureResult.class)); 299 verify(mockCaptureCallback, never()). 300 onCaptureFailed( 301 eq(info.mCameraSession), 302 eq(request), 303 isA(CaptureFailure.class)); 304 } catch (Throwable e) { 305 mCollector.addMessage(String.format( 306 "Mandatory stream combination : %s for camera id %s failed due: %s", 307 testSample.combination.getDescription(), 308 testSample.cameraId, e.getMessage())); 309 } 310 311 } 312 313 for (TestSample testSample : testSamples) { 314 if (testSample.haveSession) { 315 try { 316 Log.i(TAG, 317 String.format("Done with camera %s, combination: %s, closing session", 318 testSample.cameraId, testSample.combination.getDescription())); 319 stopCapture(testSample.cameraId, /*fast*/false); 320 } catch (Throwable e) { 321 String closingDownFormat = 322 "Closing down for combination: %s for camera id %s failed due to: %s"; 323 mCollector.addMessage( 324 String.format(closingDownFormat, 325 testSample.combination.getDescription(), testSample.cameraId, 326 e.getMessage())); 327 } 328 } 329 testSample.targets.close(); 330 } 331 } 332 } 333