1 /* 2 * Copyright (C) 2024 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.mediav2.cts; 18 19 import android.media.MediaCodec; 20 import android.util.Pair; 21 22 import androidx.annotation.NonNull; 23 24 import org.junit.Assert; 25 26 import java.util.ArrayList; 27 import java.util.HashMap; 28 import java.util.HashSet; 29 import java.util.List; 30 import java.util.Locale; 31 import java.util.Map; 32 import java.util.Objects; 33 import java.util.Set; 34 35 /** 36 * This class encapsulates information about the availability of a generic media codec resource 37 */ 38 public class CodecResource { 39 private final String mResourceId; 40 private final long mCapacity; 41 private long mAvailable; 42 CodecResource(String resourceId, long capacity, long available)43 public CodecResource(String resourceId, long capacity, long available) { 44 this.mResourceId = resourceId; 45 this.mCapacity = capacity; 46 this.mAvailable = available; 47 } 48 getResourceId()49 public String getResourceId() { 50 return mResourceId; 51 } 52 getAvailable()53 public long getAvailable() { 54 return mAvailable; 55 } 56 addAvailable(long amount)57 public void addAvailable(long amount) { 58 this.mAvailable += amount; 59 } 60 61 @Override equals(Object o)62 public boolean equals(Object o) { 63 if (this == o) return true; 64 if (!(o instanceof CodecResource)) return false; 65 CodecResource that = (CodecResource) o; 66 return (mResourceId.equals(that.mResourceId) && mCapacity == that.mCapacity 67 && mAvailable == that.mAvailable); 68 } 69 70 @Override hashCode()71 public int hashCode() { 72 return Objects.hash(mResourceId, mCapacity, mAvailable); 73 } 74 75 @NonNull 76 @Override toString()77 public String toString() { 78 return String.format(Locale.getDefault(), "Resource id %s, Capacity 0x%x, Available 0x%x", 79 mResourceId, mCapacity, mAvailable); 80 } 81 82 } 83 84 /** 85 * This class provides utility functions for managing and comparing codec resources 86 */ 87 class CodecResourceUtils { 88 private static final long CAPACITY_UNKNOWN = -1L; 89 public static final int RESOURCE_EQ = 0; 90 public static final int LHS_RESOURCE_GE = 1; 91 public static final int RHS_RESOURCE_GE = 2; 92 public static final int RESOURCE_COMPARISON_UNKNOWN = -1; 93 94 public enum CodecState { 95 UNINITIALIZED, 96 CONFIGURED, 97 FLUSHED, 98 RUNNING, 99 EOS, 100 STOPPED, 101 RELEASED; 102 toString(CodecState state)103 public static String toString(CodecState state) { 104 switch (state) { 105 case UNINITIALIZED: 106 return "un-initialized"; 107 case CONFIGURED: 108 return "configured"; 109 case FLUSHED: 110 return "flushed"; 111 case RUNNING: 112 return "running"; 113 case EOS: 114 return "end of stream"; 115 case STOPPED: 116 return "stopped"; 117 case RELEASED: 118 return "released"; 119 default: 120 return "unknown state"; 121 } 122 } 123 } 124 addResources(List<MediaCodec.InstanceResourceInfo> from, List<CodecResource> to, boolean addNewEntry)125 public static void addResources(List<MediaCodec.InstanceResourceInfo> from, 126 List<CodecResource> to, boolean addNewEntry) { 127 Map<String, CodecResource> toMap = new HashMap<>(); 128 for (CodecResource resource : to) { 129 toMap.put(resource.getResourceId(), resource); 130 } 131 132 for (MediaCodec.InstanceResourceInfo fromResource : from) { 133 long amount = fromResource.getStaticCount(); 134 CodecResource toResource = toMap.get(fromResource.getName()); 135 if (toResource != null) { 136 toResource.addAvailable(amount); 137 } else if (addNewEntry) { 138 CodecResource entry = 139 new CodecResource(fromResource.getName(), CAPACITY_UNKNOWN, amount); 140 to.add(entry); 141 toMap.put(entry.getResourceId(), entry); 142 } 143 } 144 } 145 compareResources(List<CodecResource> lhs, List<CodecResource> rhs, StringBuilder errorLogs)146 public static int compareResources(List<CodecResource> lhs, List<CodecResource> rhs, 147 StringBuilder errorLogs) { 148 if (lhs.size() != rhs.size()) { 149 if (errorLogs != null) { 150 errorLogs.append(String.format(Locale.getDefault(), 151 "resources list sizes %d, %d are not identical\n", lhs.size(), rhs.size())); 152 } 153 return RESOURCE_COMPARISON_UNKNOWN; 154 } 155 Map<String, CodecResource> lhsMap = new HashMap<>(); 156 Map<String, CodecResource> rhsMap = new HashMap<>(); 157 158 for (CodecResource resource : lhs) { 159 lhsMap.put(resource.getResourceId(), resource); 160 } 161 for (CodecResource resource : rhs) { 162 rhsMap.put(resource.getResourceId(), resource); 163 } 164 165 int equalCount = 0; 166 int lhsGreaterCount = 0; 167 int rhsGreaterCount = 0; 168 169 Set<String> allResourceIds = new HashSet<>(); 170 allResourceIds.addAll(lhsMap.keySet()); 171 allResourceIds.addAll(rhsMap.keySet()); 172 173 for (String resourceId : allResourceIds) { 174 CodecResource lhsResource = lhsMap.get(resourceId); 175 CodecResource rhsResource = rhsMap.get(resourceId); 176 if (lhsResource == null) { 177 if (errorLogs != null) { 178 errorLogs.append("lhs resource : empty, rhs resource : ").append(rhsResource) 179 .append("\n"); 180 } 181 return RESOURCE_COMPARISON_UNKNOWN; 182 } else if (rhsResource == null) { 183 if (errorLogs != null) { 184 errorLogs.append("lhs resource : ").append(lhsResource) 185 .append("rhs resource : empty").append("\n"); 186 } 187 return RESOURCE_COMPARISON_UNKNOWN; 188 } else if (lhsResource.getAvailable() == rhsResource.getAvailable()) { 189 equalCount++; 190 } else if (lhsResource.getAvailable() > rhsResource.getAvailable()) { 191 lhsGreaterCount++; 192 } else { 193 rhsGreaterCount++; 194 } 195 if (errorLogs != null) { 196 errorLogs.append("lhs resource : ").append(lhsResource).append(", rhs resource : ") 197 .append(rhsResource).append("\n"); 198 } 199 } 200 if (equalCount == lhs.size()) { 201 return RESOURCE_EQ; 202 } else if (lhsGreaterCount + equalCount == lhs.size()) { 203 return LHS_RESOURCE_GE; 204 } else if (rhsGreaterCount + equalCount == lhs.size()) { 205 return RHS_RESOURCE_GE; 206 } else { 207 return RESOURCE_COMPARISON_UNKNOWN; 208 } 209 } 210 getCurrentGlobalCodecResources()211 public static List<CodecResource> getCurrentGlobalCodecResources() { 212 List<CodecResource> currentGlobalResources = new ArrayList<>(); 213 List<MediaCodec.GlobalResourceInfo> globalResources = 214 MediaCodec.getGloballyAvailableResources(); 215 for (MediaCodec.GlobalResourceInfo resource : globalResources) { 216 CodecResource res = new CodecResource(resource.getName(), resource.getCapacity(), 217 resource.getAvailable()); 218 currentGlobalResources.add(res); 219 } 220 return currentGlobalResources; 221 } 222 223 /** 224 * This function computes the sum of current globally available resources and current active 225 * codec instance(s) consumed resources and matches them with system's globally available 226 * resources. The caller is responsible for passing all active media codec instances and 227 * system's global media codec resources. 228 * 229 * @param codecsAndStates list of media codec instance and its state. 230 * @param refResources expected global resources 231 * @param msg diagnostics to print on failure 232 */ validateGetCodecResources(List<Pair<MediaCodec, CodecState>> codecsAndStates, List<CodecResource> refResources, String msg)233 public static void validateGetCodecResources(List<Pair<MediaCodec, CodecState>> codecsAndStates, 234 List<CodecResource> refResources, String msg) { 235 boolean shouldThrowException = false; 236 for (Pair<MediaCodec, CodecState> codecAndState : codecsAndStates) { 237 boolean seenException; 238 try { 239 codecAndState.first.getRequiredResources(); 240 seenException = false; 241 } catch (IllegalStateException ignored) { 242 seenException = true; 243 } 244 shouldThrowException = (codecAndState.second == CodecState.UNINITIALIZED 245 || codecAndState.second == CodecState.STOPPED 246 || codecAndState.second == CodecState.RELEASED); 247 Assert.assertEquals(msg, shouldThrowException, seenException); 248 } 249 if (!shouldThrowException) { 250 List<CodecResource> currAvblResources = getCurrentGlobalCodecResources(); 251 StringBuilder logs = new StringBuilder(); 252 for (Pair<MediaCodec, CodecState> codecAndState : codecsAndStates) { 253 if (codecAndState.second != CodecState.CONFIGURED) { 254 List<MediaCodec.InstanceResourceInfo> instanceResources = 255 codecAndState.first.getRequiredResources(); 256 addResources(instanceResources, currAvblResources, false); 257 } 258 } 259 int result = compareResources(refResources, currAvblResources, logs); 260 Assert.assertEquals(logs.toString(), RESOURCE_EQ, result); 261 } 262 } 263 264 /** 265 * Determines the maximum percentage of resource consumption across all resources. 266 * <p> 267 * This method compares the total available resources before usage to the remaining resources 268 * afterward and calculates the percentage of each resource that has been consumed. It then 269 * returns the highest observed consumption percentage among all resources. 270 * 271 * @param globalResources list of CodecResource representing the total available resources. 272 * @param usedResources list of CodecResource representing the remaining resources after 273 * usage. 274 * @return The highest percentage of resource consumption among all resources. 275 */ computeConsumption(List<CodecResource> globalResources, List<CodecResource> usedResources)276 public static double computeConsumption(List<CodecResource> globalResources, 277 List<CodecResource> usedResources) { 278 Map<String, CodecResource> globalResourcesMap = new HashMap<>(); 279 Map<String, CodecResource> usedResourcesMap = new HashMap<>(); 280 281 for (CodecResource resource : globalResources) { 282 globalResourcesMap.put(resource.getResourceId(), resource); 283 } 284 for (CodecResource resource : usedResources) { 285 usedResourcesMap.put(resource.getResourceId(), resource); 286 } 287 288 double max = 0; 289 for (Map.Entry<String, CodecResource> global : globalResourcesMap.entrySet()) { 290 CodecResource used = usedResourcesMap.get(global.getKey()); 291 if (used != null) { 292 double result = (double) (global.getValue().getAvailable() - used.getAvailable()) 293 / global.getValue().getAvailable() * 100; 294 max = Math.max(max, result); 295 } 296 } 297 return max; 298 } 299 } 300