1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 * in compliance with the License. You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the License
10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 * or implied. See the License for the specific language governing permissions and limitations under
12 * the License.
13 */
14
15package androidx.media.filterfw;
16
17
18/**
19 * A FrameType instance specifies the data format of a Frame.
20 *
21 * FrameTypes are used mainly by Filters to specify the data type they intend to consume or produce.
22 * When filters are connected, their FrameType information is analyzed and checked for
23 * compatibility. This allows Filter writers to assume a certain data input type. It also helps
24 * filter-graph designers determine which filters can be hooked up to one another.
25 *
26 * A FrameType generally consists of an element type and number of dimensions. The currently
27 * supported element types are:
28 *
29 * <ul>
30 * <li>int8, int16, int32, in64</li>
31 * <li>float32, float64</li>
32 * <li>rgba8888</li>
33 * <li>object</li>
34 * <li>don't-care</li>
35 * </ul>
36 *
37 * If the object element type is used, class information may be appended to the FrameType to
38 * indicate what class of objects are expected. When constructing an object based FrameType, you
39 * have the option of either specifying a type that represents a single object of that class, or
40 * an array of objects (see the {@link #single()} and {@link #array()} constructors). A single
41 * object has a dimensionality of 0, while an array has a dimensionality of 1.
42 *
43 * When constructing a non-object type, you have the option of creating a 1D or 2D buffer, or
44 * a 2D image (see the {@link #buffer1D(int)}, {@link #buffer2D(int)}, and
45 * {@link #image2D(int, int)} constructors). To optimize access, provide access hints when making
46 * an image type.
47 *
48 * Finally, it is possible to create a wild-card type with the {@link #any()} constructor. This
49 * type matches any other type. Note, that this is a more general type than a {@code single(Object)}
50 * type that matches only object-base types (of any Object subclass). You may also specify the
51 * leave the element of any type unspecified by using the {@code ELEMENT_DONTCARE} constant.
52 *
53 * When a graph is connected the types between outputs and inputs are merged to a queue-type. All
54 * Frames in this queue will be of that type. In order for a merge to succeed the following
55 * conditions must hold:
56 *
57 * <ul>
58 * <li>The element types must be identical.</li>
59 * <li>The dimensions must match (except for singles and arrays, see below).</li>
60 * <li>For object-based types: The classes must be compatible.</li>
61 * <li>If one of the types is a wild-card, both types are always compatible.</li>
62 * </ul>
63 *
64 * Class compatibility is determined in an optimistic fashion, i.e. one class must be the subclass
65 * of the other. It does not matter which of the types is the subclass of the other. For instance,
66 * if one Filter outputs a type of class {@code Object}, and the consumer expects a Filter of type
67 * {@code Bitmap}, the connection is considered compatible. (Of course if at runtime a non-Bitmap
68 * object is produced, this will cause a runtime exception to be thrown).
69 *
70 * For convenience, single and array object-based types are compatible with one another. This
71 * in turn means that Frames with a single object can be accessed as an array with a single entry,
72 * and array based Frames can be accessed as a single object of the array class. For this reason
73 * you should prefer consuming objects as array types (if it makes sense for that specific port),
74 * as this will allow your Filter to handle multiple objects in one Frame while not giving up the
75 * possibility to deal with singles.
76 * TODO: This needs to be reworked. An array(int) should not be interchangeable with a single(int),
77 * but rather with a single(int[]). Use ArraySelectFilter for the former!
78 *
79 * After the types are merged, the queue-type must be a fully specified type. This means that the
80 * type must have its element and dimensions specified. This ensures that filters that need to
81 * query their input or output types receive meaningful information.
82 */
83public final class FrameType {
84
85    public final static int ELEMENT_DONTCARE = 0;
86    public final static int ELEMENT_OBJECT = 1;
87
88    public final static int ELEMENT_INT8 = 100;
89    public final static int ELEMENT_INT16 = 101;
90    public final static int ELEMENT_INT32 = 102;
91    public final static int ELEMENT_INT64 = 103;
92
93    public final static int ELEMENT_FLOAT32 = 200;
94    public final static int ELEMENT_FLOAT64 = 201;
95
96    public final static int ELEMENT_RGBA8888 = 301;
97
98    public final static int READ_CPU = 0x01;
99    public final static int READ_GPU = 0x02;
100    public final static int READ_ALLOCATION = 0x04;
101    public final static int WRITE_CPU = 0x08;
102    public final static int WRITE_GPU = 0x10;
103    public final static int WRITE_ALLOCATION = 0x20;
104
105    private final static int ACCESS_UNKNOWN = 0x00;
106
107    private final int mElementId;
108    private final int mDimensions;
109    private final int mAccessHints;
110    private final Class<?> mClass;
111
112    private static SimpleCache<String, FrameType> mTypeCache =
113            new SimpleCache<String, FrameType>(64);
114
115    /**
116     * Constructs a wild-card FrameType that matches any other FrameType.
117     * @return The wild-card FrameType instance.
118     */
119    public static FrameType any() {
120        return FrameType.fetchType(ELEMENT_DONTCARE, -1, ACCESS_UNKNOWN);
121    }
122
123    /**
124     * Constructs an object-based single FrameType that matches object-based FrameTypes of any
125     * class.
126     * @return A single object-based FrameType instance.
127     */
128    public static FrameType single() {
129        return FrameType.fetchType(null, 0);
130    }
131
132    /**
133     * Constructs an object-based single FrameType of the specified class.
134     * @param clazz The class of the FrameType.
135     * @return A single object-base FrameType instance of the specified class.
136     */
137    public static FrameType single(Class<?> clazz) {
138        return FrameType.fetchType(clazz, 0);
139    }
140
141    /**
142     * Constructs an object-based array FrameType that matches object-based FrameTypes of any class.
143     * @return An array object-based FrameType instance.
144     */
145    public static FrameType array() {
146        return FrameType.fetchType(null, 1);
147    }
148
149    /**
150     * Constructs an object-based array FrameType with elements of the specified class.
151     * @param clazz The class of the array elements (not the array type).
152     * @return An array object-based FrameType instance of the specified class.
153     */
154    public static FrameType array(Class<?> clazz) {
155        return FrameType.fetchType(clazz, 1);
156    }
157
158    /**
159     * Constructs a one-dimensional buffer type of the specified element.
160     * @param elementType One of the {@code ELEMENT} constants.
161     * @return A 1D buffer FrameType instance.
162     */
163    public static FrameType buffer1D(int elementType) {
164        return FrameType.fetchType(elementType, 1, ACCESS_UNKNOWN);
165    }
166
167    /**
168     * Constructs a two-dimensional buffer type of the specified element.
169     * @param elementType One of the {@code ELEMENT} constants.
170     * @return A 2D buffer FrameType instance.
171     */
172    public static FrameType buffer2D(int elementType) {
173        return FrameType.fetchType(elementType, 2, ACCESS_UNKNOWN);
174    }
175
176    /**
177     * Constructs a two-dimensional image type of the specified element.
178     * @param elementType One of the {@code ELEMENT} constants.
179     * @param accessHint A bit-mask of access flags (see {@code READ} and {@code WRITE} constants).
180     * @return A 2D image FrameType instance.
181     */
182    public static FrameType image2D(int elementType, int accessHint) {
183        return FrameType.fetchType(elementType, 2, accessHint);
184    }
185
186    /**
187     * Converts the current array type to a single type.
188     * The type must be an object-based type. If the type is already a single type, this does
189     * nothing.
190     * @return type as a single type.
191     */
192    public FrameType asSingle() {
193        if (mElementId != ELEMENT_OBJECT) {
194            throw new RuntimeException("Calling asSingle() on non-object type!");
195        }
196        return FrameType.fetchType(mClass, 0);
197    }
198
199    /**
200     * Converts the current single type to an array type.
201     * The type must be an object-based type. If the type is already an array type, this does
202     * nothing.
203     * @return type as an array type.
204     */
205    public FrameType asArray() {
206        if (mElementId != ELEMENT_OBJECT) {
207            throw new RuntimeException("Calling asArray() on non-object type!");
208        }
209        return FrameType.fetchType(mClass, 1);
210    }
211
212    /**
213     * Returns the FrameType's class specifier, or null if no class was set or the receiver is not
214     * an object-based type.
215     * @return The FrameType's class specifier or null.
216     */
217    public Class<?> getContentClass() {
218        return mClass;
219    }
220
221    /**
222     * Returns the FrameType's element id.
223     * @return The element id constant.
224     */
225    public int getElementId() {
226        return mElementId;
227    }
228
229    /**
230     * Returns the number of bytes of the FrameType's element, or 0 if no such size can be
231     * determined.
232     * @return The number of bytes of the FrameType's element.
233     */
234    public int getElementSize() {
235        switch (mElementId) {
236            case ELEMENT_INT8:
237                return 1;
238            case ELEMENT_INT16:
239                return 2;
240            case ELEMENT_INT32:
241            case ELEMENT_FLOAT32:
242            case ELEMENT_RGBA8888:
243                return 4;
244            case ELEMENT_INT64:
245            case ELEMENT_FLOAT64:
246                return 4;
247            default:
248                return 0;
249        }
250    }
251
252    /**
253     * Returns the access hints bit-mask of the FrameType.
254     * @return The access hints bit-mask of the FrameType.
255     */
256    public int getAccessHints() {
257        return mAccessHints;
258    }
259
260    /**
261     * Returns the number of dimensions of the FrameType or -1 if no dimensions were set.
262     * @return The number of dimensions of the FrameType.
263     */
264    public int getNumberOfDimensions() {
265        return mDimensions;
266    }
267
268    /**
269     * Returns true, if the FrameType is fully specified.
270     *
271     * A FrameType is fully specified if its element and dimensions are specified.
272     *
273     * @return true, if the FrameType is fully specified.
274     */
275    public boolean isSpecified() {
276        return mElementId != ELEMENT_DONTCARE && mDimensions >= 0;
277    }
278
279    @Override
280    public boolean equals(Object object) {
281        if (object instanceof FrameType) {
282            FrameType type = (FrameType) object;
283            return mElementId == type.mElementId && mDimensions == type.mDimensions
284                    && mAccessHints == type.mAccessHints && mClass == type.mClass;
285        }
286        return false;
287    }
288
289    @Override
290    public int hashCode() {
291        return mElementId ^ mDimensions ^ mAccessHints ^ mClass.hashCode();
292    }
293
294    @Override
295    public String toString() {
296        String result = elementToString(mElementId, mClass) + "[" + mDimensions + "]";
297        if ((mAccessHints & READ_CPU) != 0) {
298            result += "(rcpu)";
299        }
300        if ((mAccessHints & READ_GPU) != 0) {
301            result += "(rgpu)";
302        }
303        if ((mAccessHints & READ_ALLOCATION) != 0) {
304            result += "(ralloc)";
305        }
306        if ((mAccessHints & WRITE_CPU) != 0) {
307            result += "(wcpu)";
308        }
309        if ((mAccessHints & WRITE_GPU) != 0) {
310            result += "(wgpu)";
311        }
312        if ((mAccessHints & WRITE_ALLOCATION) != 0) {
313            result += "(walloc)";
314        }
315        return result;
316    }
317
318    String keyString() {
319        return keyValueForType(mElementId, mDimensions, mAccessHints, mClass);
320    }
321
322    static FrameType tryMerge(FrameType writer, FrameType reader) {
323        if (writer.mElementId == ELEMENT_DONTCARE) {
324            return reader;
325        } else if (reader.mElementId == ELEMENT_DONTCARE) {
326            return writer;
327        } else if (writer.mElementId == ELEMENT_OBJECT && reader.mElementId == ELEMENT_OBJECT) {
328            return tryMergeObjectTypes(writer, reader);
329        } else if (writer.mDimensions > 0 && writer.mElementId == reader.mElementId) {
330            return tryMergeBuffers(writer, reader);
331        } else {
332            return null;
333        }
334    }
335
336    static FrameType tryMergeObjectTypes(FrameType writer, FrameType reader) {
337        int dimensions = Math.max(writer.mDimensions, reader.mDimensions);
338        Class<?> mergedClass = mergeClasses(writer.mClass, reader.mClass);
339        boolean success = mergedClass != null || writer.mClass == null;
340        return success ? FrameType.fetchType(mergedClass, dimensions) : null;
341    }
342
343    static FrameType tryMergeBuffers(FrameType writer, FrameType reader) {
344        if (writer.mDimensions == reader.mDimensions) {
345            int accessHints = writer.mAccessHints | reader.mAccessHints;
346            return FrameType.fetchType(writer.mElementId, writer.mDimensions, accessHints);
347        }
348        return null;
349    }
350
351    static FrameType merge(FrameType writer, FrameType reader) {
352        FrameType result = tryMerge(writer, reader);
353        if (result == null) {
354            throw new RuntimeException(
355                    "Incompatible types in connection: " + writer + " vs. " + reader + "!");
356        }
357        return result;
358    }
359
360    private static String keyValueForType(int elemId, int dims, int hints, Class<?> clazz) {
361        return elemId + ":" + dims + ":" + hints + ":" + (clazz != null ? clazz.getName() : "0");
362    }
363
364    private static String elementToString(int elemId, Class<?> clazz) {
365        switch (elemId) {
366            case ELEMENT_INT8:
367                return "int8";
368            case ELEMENT_INT16:
369                return "int16";
370            case ELEMENT_INT32:
371                return "int32";
372            case ELEMENT_INT64:
373                return "int64";
374            case ELEMENT_FLOAT32:
375                return "float32";
376            case ELEMENT_FLOAT64:
377                return "float64";
378            case ELEMENT_RGBA8888:
379                return "rgba8888";
380            case ELEMENT_OBJECT:
381                return "<" + (clazz == null ? "*" : clazz.getSimpleName()) + ">";
382            case ELEMENT_DONTCARE:
383                return "*";
384            default:
385                return "?";
386        }
387    }
388
389    private static Class<?> mergeClasses(Class<?> classA, Class<?> classB) {
390        // Return the most specialized class.
391        if (classA == null) {
392            return classB;
393        } else if (classB == null) {
394            return classA;
395        } else if (classA.isAssignableFrom(classB)) {
396            return classB;
397        } else if (classB.isAssignableFrom(classA)) {
398            return classA;
399        } else {
400            return null;
401        }
402    }
403
404    private static FrameType fetchType(int elementId, int dimensions, int accessHints) {
405        return fetchType(elementId, dimensions, accessHints, null);
406    }
407
408    private static FrameType fetchType(Class<?> clazz, int dimensions) {
409        return fetchType(ELEMENT_OBJECT, dimensions, ACCESS_UNKNOWN, clazz);
410    }
411
412    private static FrameType fetchType(
413            int elementId, int dimensions, int accessHints, Class<?> clazz) {
414        String typeKey = FrameType.keyValueForType(elementId, dimensions, accessHints, clazz);
415        FrameType type = mTypeCache.get(typeKey);
416        if (type == null) {
417            type = new FrameType(elementId, dimensions, accessHints, clazz);
418            mTypeCache.put(typeKey, type);
419        }
420        return type;
421    }
422
423    private FrameType(int elementId, int dimensions, int accessHints, Class<?> clazz) {
424        mElementId = elementId;
425        mDimensions = dimensions;
426        mClass = clazz;
427        mAccessHints = accessHints;
428    }
429
430}
431