blob: bfa4018201a6ca8962e0ebd41c7c780effd95117 [file] [log] [blame]
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package androidx.media.filterfw;
/**
* A FrameType instance specifies the data format of a Frame.
*
* FrameTypes are used mainly by Filters to specify the data type they intend to consume or produce.
* When filters are connected, their FrameType information is analyzed and checked for
* compatibility. This allows Filter writers to assume a certain data input type. It also helps
* filter-graph designers determine which filters can be hooked up to one another.
*
* A FrameType generally consists of an element type and number of dimensions. The currently
* supported element types are:
*
* <ul>
* <li>int8, int16, int32, in64</li>
* <li>float32, float64</li>
* <li>rgba8888</li>
* <li>object</li>
* <li>don't-care</li>
* </ul>
*
* If the object element type is used, class information may be appended to the FrameType to
* indicate what class of objects are expected. When constructing an object based FrameType, you
* have the option of either specifying a type that represents a single object of that class, or
* an array of objects (see the {@link #single()} and {@link #array()} constructors). A single
* object has a dimensionality of 0, while an array has a dimensionality of 1.
*
* When constructing a non-object type, you have the option of creating a 1D or 2D buffer, or
* a 2D image (see the {@link #buffer1D(int)}, {@link #buffer2D(int)}, and
* {@link #image2D(int, int)} constructors). To optimize access, provide access hints when making
* an image type.
*
* Finally, it is possible to create a wild-card type with the {@link #any()} constructor. This
* type matches any other type. Note, that this is a more general type than a {@code single(Object)}
* type that matches only object-base types (of any Object subclass). You may also specify the
* leave the element of any type unspecified by using the {@code ELEMENT_DONTCARE} constant.
*
* When a graph is connected the types between outputs and inputs are merged to a queue-type. All
* Frames in this queue will be of that type. In order for a merge to succeed the following
* conditions must hold:
*
* <ul>
* <li>The element types must be identical.</li>
* <li>The dimensions must match (except for singles and arrays, see below).</li>
* <li>For object-based types: The classes must be compatible.</li>
* <li>If one of the types is a wild-card, both types are always compatible.</li>
* </ul>
*
* Class compatibility is determined in an optimistic fashion, i.e. one class must be the subclass
* of the other. It does not matter which of the types is the subclass of the other. For instance,
* if one Filter outputs a type of class {@code Object}, and the consumer expects a Filter of type
* {@code Bitmap}, the connection is considered compatible. (Of course if at runtime a non-Bitmap
* object is produced, this will cause a runtime exception to be thrown).
*
* For convenience, single and array object-based types are compatible with one another. This
* in turn means that Frames with a single object can be accessed as an array with a single entry,
* and array based Frames can be accessed as a single object of the array class. For this reason
* you should prefer consuming objects as array types (if it makes sense for that specific port),
* as this will allow your Filter to handle multiple objects in one Frame while not giving up the
* possibility to deal with singles.
* TODO: This needs to be reworked. An array(int) should not be interchangeable with a single(int),
* but rather with a single(int[]). Use ArraySelectFilter for the former!
*
* After the types are merged, the queue-type must be a fully specified type. This means that the
* type must have its element and dimensions specified. This ensures that filters that need to
* query their input or output types receive meaningful information.
*/
public final class FrameType {
public final static int ELEMENT_DONTCARE = 0;
public final static int ELEMENT_OBJECT = 1;
public final static int ELEMENT_INT8 = 100;
public final static int ELEMENT_INT16 = 101;
public final static int ELEMENT_INT32 = 102;
public final static int ELEMENT_INT64 = 103;
public final static int ELEMENT_FLOAT32 = 200;
public final static int ELEMENT_FLOAT64 = 201;
public final static int ELEMENT_RGBA8888 = 301;
public final static int READ_CPU = 0x01;
public final static int READ_GPU = 0x02;
public final static int READ_ALLOCATION = 0x04;
public final static int WRITE_CPU = 0x08;
public final static int WRITE_GPU = 0x10;
public final static int WRITE_ALLOCATION = 0x20;
private final static int ACCESS_UNKNOWN = 0x00;
private final int mElementId;
private final int mDimensions;
private final int mAccessHints;
private final Class<?> mClass;
private static SimpleCache<String, FrameType> mTypeCache =
new SimpleCache<String, FrameType>(64);
/**
* Constructs a wild-card FrameType that matches any other FrameType.
* @return The wild-card FrameType instance.
*/
public static FrameType any() {
return FrameType.fetchType(ELEMENT_DONTCARE, -1, ACCESS_UNKNOWN);
}
/**
* Constructs an object-based single FrameType that matches object-based FrameTypes of any
* class.
* @return A single object-based FrameType instance.
*/
public static FrameType single() {
return FrameType.fetchType(null, 0);
}
/**
* Constructs an object-based single FrameType of the specified class.
* @param clazz The class of the FrameType.
* @return A single object-base FrameType instance of the specified class.
*/
public static FrameType single(Class<?> clazz) {
return FrameType.fetchType(clazz, 0);
}
/**
* Constructs an object-based array FrameType that matches object-based FrameTypes of any class.
* @return An array object-based FrameType instance.
*/
public static FrameType array() {
return FrameType.fetchType(null, 1);
}
/**
* Constructs an object-based array FrameType with elements of the specified class.
* @param clazz The class of the array elements (not the array type).
* @return An array object-based FrameType instance of the specified class.
*/
public static FrameType array(Class<?> clazz) {
return FrameType.fetchType(clazz, 1);
}
/**
* Constructs a one-dimensional buffer type of the specified element.
* @param elementType One of the {@code ELEMENT} constants.
* @return A 1D buffer FrameType instance.
*/
public static FrameType buffer1D(int elementType) {
return FrameType.fetchType(elementType, 1, ACCESS_UNKNOWN);
}
/**
* Constructs a two-dimensional buffer type of the specified element.
* @param elementType One of the {@code ELEMENT} constants.
* @return A 2D buffer FrameType instance.
*/
public static FrameType buffer2D(int elementType) {
return FrameType.fetchType(elementType, 2, ACCESS_UNKNOWN);
}
/**
* Constructs a two-dimensional image type of the specified element.
* @param elementType One of the {@code ELEMENT} constants.
* @param accessHint A bit-mask of access flags (see {@code READ} and {@code WRITE} constants).
* @return A 2D image FrameType instance.
*/
public static FrameType image2D(int elementType, int accessHint) {
return FrameType.fetchType(elementType, 2, accessHint);
}
/**
* Converts the current array type to a single type.
* The type must be an object-based type. If the type is already a single type, this does
* nothing.
* @return type as a single type.
*/
public FrameType asSingle() {
if (mElementId != ELEMENT_OBJECT) {
throw new RuntimeException("Calling asSingle() on non-object type!");
}
return FrameType.fetchType(mClass, 0);
}
/**
* Converts the current single type to an array type.
* The type must be an object-based type. If the type is already an array type, this does
* nothing.
* @return type as an array type.
*/
public FrameType asArray() {
if (mElementId != ELEMENT_OBJECT) {
throw new RuntimeException("Calling asArray() on non-object type!");
}
return FrameType.fetchType(mClass, 1);
}
/**
* Returns the FrameType's class specifier, or null if no class was set or the receiver is not
* an object-based type.
* @return The FrameType's class specifier or null.
*/
public Class<?> getContentClass() {
return mClass;
}
/**
* Returns the FrameType's element id.
* @return The element id constant.
*/
public int getElementId() {
return mElementId;
}
/**
* Returns the number of bytes of the FrameType's element, or 0 if no such size can be
* determined.
* @return The number of bytes of the FrameType's element.
*/
public int getElementSize() {
switch (mElementId) {
case ELEMENT_INT8:
return 1;
case ELEMENT_INT16:
return 2;
case ELEMENT_INT32:
case ELEMENT_FLOAT32:
case ELEMENT_RGBA8888:
return 4;
case ELEMENT_INT64:
case ELEMENT_FLOAT64:
return 4;
default:
return 0;
}
}
/**
* Returns the access hints bit-mask of the FrameType.
* @return The access hints bit-mask of the FrameType.
*/
public int getAccessHints() {
return mAccessHints;
}
/**
* Returns the number of dimensions of the FrameType or -1 if no dimensions were set.
* @return The number of dimensions of the FrameType.
*/
public int getNumberOfDimensions() {
return mDimensions;
}
/**
* Returns true, if the FrameType is fully specified.
*
* A FrameType is fully specified if its element and dimensions are specified.
*
* @return true, if the FrameType is fully specified.
*/
public boolean isSpecified() {
return mElementId != ELEMENT_DONTCARE && mDimensions >= 0;
}
@Override
public boolean equals(Object object) {
if (object instanceof FrameType) {
FrameType type = (FrameType) object;
return mElementId == type.mElementId && mDimensions == type.mDimensions
&& mAccessHints == type.mAccessHints && mClass == type.mClass;
}
return false;
}
@Override
public int hashCode() {
return mElementId ^ mDimensions ^ mAccessHints ^ mClass.hashCode();
}
@Override
public String toString() {
String result = elementToString(mElementId, mClass) + "[" + mDimensions + "]";
if ((mAccessHints & READ_CPU) != 0) {
result += "(rcpu)";
}
if ((mAccessHints & READ_GPU) != 0) {
result += "(rgpu)";
}
if ((mAccessHints & READ_ALLOCATION) != 0) {
result += "(ralloc)";
}
if ((mAccessHints & WRITE_CPU) != 0) {
result += "(wcpu)";
}
if ((mAccessHints & WRITE_GPU) != 0) {
result += "(wgpu)";
}
if ((mAccessHints & WRITE_ALLOCATION) != 0) {
result += "(walloc)";
}
return result;
}
String keyString() {
return keyValueForType(mElementId, mDimensions, mAccessHints, mClass);
}
static FrameType tryMerge(FrameType writer, FrameType reader) {
if (writer.mElementId == ELEMENT_DONTCARE) {
return reader;
} else if (reader.mElementId == ELEMENT_DONTCARE) {
return writer;
} else if (writer.mElementId == ELEMENT_OBJECT && reader.mElementId == ELEMENT_OBJECT) {
return tryMergeObjectTypes(writer, reader);
} else if (writer.mDimensions > 0 && writer.mElementId == reader.mElementId) {
return tryMergeBuffers(writer, reader);
} else {
return null;
}
}
static FrameType tryMergeObjectTypes(FrameType writer, FrameType reader) {
int dimensions = Math.max(writer.mDimensions, reader.mDimensions);
Class<?> mergedClass = mergeClasses(writer.mClass, reader.mClass);
boolean success = mergedClass != null || writer.mClass == null;
return success ? FrameType.fetchType(mergedClass, dimensions) : null;
}
static FrameType tryMergeBuffers(FrameType writer, FrameType reader) {
if (writer.mDimensions == reader.mDimensions) {
int accessHints = writer.mAccessHints | reader.mAccessHints;
return FrameType.fetchType(writer.mElementId, writer.mDimensions, accessHints);
}
return null;
}
static FrameType merge(FrameType writer, FrameType reader) {
FrameType result = tryMerge(writer, reader);
if (result == null) {
throw new RuntimeException(
"Incompatible types in connection: " + writer + " vs. " + reader + "!");
}
return result;
}
private static String keyValueForType(int elemId, int dims, int hints, Class<?> clazz) {
return elemId + ":" + dims + ":" + hints + ":" + (clazz != null ? clazz.getName() : "0");
}
private static String elementToString(int elemId, Class<?> clazz) {
switch (elemId) {
case ELEMENT_INT8:
return "int8";
case ELEMENT_INT16:
return "int16";
case ELEMENT_INT32:
return "int32";
case ELEMENT_INT64:
return "int64";
case ELEMENT_FLOAT32:
return "float32";
case ELEMENT_FLOAT64:
return "float64";
case ELEMENT_RGBA8888:
return "rgba8888";
case ELEMENT_OBJECT:
return "<" + (clazz == null ? "*" : clazz.getSimpleName()) + ">";
case ELEMENT_DONTCARE:
return "*";
default:
return "?";
}
}
private static Class<?> mergeClasses(Class<?> classA, Class<?> classB) {
// Return the most specialized class.
if (classA == null) {
return classB;
} else if (classB == null) {
return classA;
} else if (classA.isAssignableFrom(classB)) {
return classB;
} else if (classB.isAssignableFrom(classA)) {
return classA;
} else {
return null;
}
}
private static FrameType fetchType(int elementId, int dimensions, int accessHints) {
return fetchType(elementId, dimensions, accessHints, null);
}
private static FrameType fetchType(Class<?> clazz, int dimensions) {
return fetchType(ELEMENT_OBJECT, dimensions, ACCESS_UNKNOWN, clazz);
}
private static FrameType fetchType(
int elementId, int dimensions, int accessHints, Class<?> clazz) {
String typeKey = FrameType.keyValueForType(elementId, dimensions, accessHints, clazz);
FrameType type = mTypeCache.get(typeKey);
if (type == null) {
type = new FrameType(elementId, dimensions, accessHints, clazz);
mTypeCache.put(typeKey, type);
}
return type;
}
private FrameType(int elementId, int dimensions, int accessHints, Class<?> clazz) {
mElementId = elementId;
mDimensions = dimensions;
mClass = clazz;
mAccessHints = accessHints;
}
}