• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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