| /* |
| * Copyright (C) 2020 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 android.media; |
| |
| import android.annotation.IntRange; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.util.Log; |
| import android.util.Pair; |
| |
| import java.lang.reflect.ParameterizedType; |
| import java.nio.BufferUnderflowException; |
| import java.nio.ByteBuffer; |
| import java.nio.ByteOrder; |
| import java.nio.charset.Charset; |
| import java.nio.charset.StandardCharsets; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Set; |
| |
| /** |
| * AudioMetadata class is used to manage typed key-value pairs for |
| * configuration and capability requests within the Audio Framework. |
| */ |
| public final class AudioMetadata { |
| private static final String TAG = "AudioMetadata"; |
| |
| /** |
| * Key interface for the {@code AudioMetadata} map. |
| * |
| * <p>The presence of this {@code Key} interface on an object allows |
| * it to reference metadata in the Audio Framework.</p> |
| * |
| * <p>Vendors are allowed to implement this {@code Key} interface for their debugging or |
| * private application use. To avoid name conflicts, vendor key names should be qualified by |
| * the vendor company name followed by a dot; for example, "vendorCompany.someVolume".</p> |
| * |
| * @param <T> type of value associated with {@code Key}. |
| */ |
| /* |
| * Internal details: |
| * Conceivably metadata keys exposing multiple interfaces |
| * could be eligible to work in multiple framework domains. |
| */ |
| public interface Key<T> { |
| /** |
| * Returns the internal name of the key. The name should be unique in the |
| * {@code AudioMetadata} namespace. Vendors should prefix their keys with |
| * the company name followed by a dot. |
| */ |
| @NonNull |
| String getName(); |
| |
| /** |
| * Returns the class type {@code T} of the associated value. Valid class types for |
| * {@link android.os.Build.VERSION_CODES#R} are |
| * {@code Integer.class}, {@code Long.class}, {@code Float.class}, {@code Double.class}, |
| * {@code String.class}. |
| */ |
| @NonNull |
| Class<T> getValueClass(); |
| |
| // TODO: consider adding bool isValid(@NonNull T value) |
| } |
| |
| /** |
| * Creates a {@link AudioMetadataMap} suitable for adding keys. |
| * @return an empty {@link AudioMetadataMap} instance. |
| */ |
| @NonNull |
| public static AudioMetadataMap createMap() { |
| return new BaseMap(); |
| } |
| |
| /** |
| * A container class for AudioMetadata Format keys. |
| * |
| * @see AudioTrack.OnCodecFormatChangedListener |
| */ |
| public static class Format { |
| // The key name strings used here must match that of the native framework, but are |
| // allowed to change between API releases. This due to the Java specification |
| // on what is a compile time constant. |
| // |
| // Key<?> are final variables but not constant variables (per Java spec 4.12.4) because |
| // the keys are not a primitive type nor a String initialized by a constant expression. |
| // Hence (per Java spec 13.1.3), they are not resolved at compile time, |
| // rather are picked up by applications at run time. |
| // |
| // So the contractual API behavior of AudioMetadata.Key<> are different than Strings |
| // initialized by a constant expression (for example MediaFormat.KEY_*). |
| |
| // See MediaFormat |
| /** |
| * A key representing the bitrate of the encoded stream used in |
| * |
| * If the stream is variable bitrate, this is the average bitrate of the stream. |
| * The unit is bits per second. |
| * |
| * An Integer value. |
| * |
| * @see MediaFormat#KEY_BIT_RATE |
| */ |
| @NonNull public static final Key<Integer> KEY_BIT_RATE = |
| createKey("bitrate", Integer.class); |
| |
| /** |
| * A key representing the audio channel mask of the stream. |
| * |
| * An Integer value. |
| * |
| * @see AudioTrack#getChannelConfiguration() |
| * @see MediaFormat#KEY_CHANNEL_MASK |
| */ |
| @NonNull public static final Key<Integer> KEY_CHANNEL_MASK = |
| createKey("channel-mask", Integer.class); |
| |
| |
| /** |
| * A key representing the codec mime string. |
| * |
| * A String value. |
| * |
| * @see MediaFormat#KEY_MIME |
| */ |
| @NonNull public static final Key<String> KEY_MIME = createKey("mime", String.class); |
| |
| /** |
| * A key representing the audio sample rate in Hz of the stream. |
| * |
| * An Integer value. |
| * |
| * @see AudioFormat#getSampleRate() |
| * @see MediaFormat#KEY_SAMPLE_RATE |
| */ |
| @NonNull public static final Key<Integer> KEY_SAMPLE_RATE = |
| createKey("sample-rate", Integer.class); |
| |
| // Unique to Audio |
| |
| /** |
| * A key representing the bit width of an element of decoded data. |
| * |
| * An Integer value. |
| */ |
| @NonNull public static final Key<Integer> KEY_BIT_WIDTH = |
| createKey("bit-width", Integer.class); |
| |
| /** |
| * A key representing the presence of Atmos in an E-AC3 stream. |
| * |
| * A Boolean value which is true if Atmos is present in an E-AC3 stream. |
| */ |
| |
| // Since Boolean isn't handled by Parceling, we translate |
| // internally to KEY_HAS_ATMOS when sending through JNI. |
| // Consider deprecating this key for KEY_HAS_ATMOS in the future. |
| // |
| @NonNull public static final Key<Boolean> KEY_ATMOS_PRESENT = |
| createKey("atmos-present", Boolean.class); |
| |
| /** |
| * A key representing the presence of Atmos in an E-AC3 stream. |
| * |
| * An Integer value which is nonzero if Atmos is present in an E-AC3 stream. |
| * The integer representation is used for communication to the native side. |
| * @hide |
| */ |
| @NonNull public static final Key<Integer> KEY_HAS_ATMOS = |
| createKey("has-atmos", Integer.class); |
| |
| /** |
| * A key representing the audio encoding used for the stream. |
| * This is the same encoding used in {@link AudioFormat#getEncoding()}. |
| * |
| * An Integer value. |
| * |
| * @see AudioFormat#getEncoding() |
| */ |
| @NonNull public static final Key<Integer> KEY_AUDIO_ENCODING = |
| createKey("audio-encoding", Integer.class); |
| |
| |
| /** |
| * A key representing the audio presentation id being decoded by a next generation |
| * audio decoder. |
| * |
| * An Integer value representing presentation id. |
| * |
| * @see AudioPresentation#getPresentationId() |
| */ |
| @NonNull public static final Key<Integer> KEY_PRESENTATION_ID = |
| createKey("presentation-id", Integer.class); |
| |
| /** |
| * A key representing the audio program id being decoded by a next generation |
| * audio decoder. |
| * |
| * An Integer value representing program id. |
| * |
| * @see AudioPresentation#getProgramId() |
| */ |
| @NonNull public static final Key<Integer> KEY_PROGRAM_ID = |
| createKey("program-id", Integer.class); |
| |
| |
| /** |
| * A key representing the audio presentation content classifier being rendered |
| * by a next generation audio decoder. |
| * |
| * An Integer value representing presentation content classifier. |
| * |
| * @see AudioPresentation#CONTENT_UNKNOWN |
| * @see AudioPresentation#CONTENT_MAIN |
| * @see AudioPresentation#CONTENT_MUSIC_AND_EFFECTS |
| * @see AudioPresentation#CONTENT_VISUALLY_IMPAIRED |
| * @see AudioPresentation#CONTENT_HEARING_IMPAIRED |
| * @see AudioPresentation#CONTENT_DIALOG |
| * @see AudioPresentation#CONTENT_COMMENTARY |
| * @see AudioPresentation#CONTENT_EMERGENCY |
| * @see AudioPresentation#CONTENT_VOICEOVER |
| */ |
| @NonNull public static final Key<Integer> KEY_PRESENTATION_CONTENT_CLASSIFIER = |
| createKey("presentation-content-classifier", Integer.class); |
| |
| /** |
| * A key representing the audio presentation language being rendered by a next |
| * generation audio decoder. |
| * |
| * A String value representing ISO 639-2 (three letter code). |
| * |
| * @see AudioPresentation#getLocale() |
| */ |
| @NonNull public static final Key<String> KEY_PRESENTATION_LANGUAGE = |
| createKey("presentation-language", String.class); |
| |
| private Format() {} // delete constructor |
| } |
| |
| ///////////////////////////////////////////////////////////////////////// |
| // Hidden methods and functions. |
| |
| /** |
| * Returns a Key object with the correct interface for the AudioMetadata. |
| * |
| * An interface with the same name and type will be treated as |
| * identical for the purposes of value storage, even though |
| * other methods or hidden parameters may return different values. |
| * |
| * @param name The name of the key. |
| * @param type The class type of the value represented by the key. |
| * @param <T> The type of value. |
| * @return a new key interface. |
| * |
| * Creating keys is currently only allowed by the Framework. |
| * @hide |
| */ |
| @NonNull |
| public static <T> Key<T> createKey(@NonNull String name, @NonNull Class<T> type) { |
| // Implementation specific. |
| return new Key<T>() { |
| private final String mName = name; |
| private final Class<T> mType = type; |
| |
| @Override |
| @NonNull |
| public String getName() { |
| return mName; |
| } |
| |
| @Override |
| @NonNull |
| public Class<T> getValueClass() { |
| return mType; |
| } |
| |
| /** |
| * Return true if the name and the type of two objects are the same. |
| */ |
| @Override |
| public boolean equals(Object obj) { |
| if (obj == this) { |
| return true; |
| } |
| if (!(obj instanceof Key)) { |
| return false; |
| } |
| Key<?> other = (Key<?>) obj; |
| return mName.equals(other.getName()) && mType.equals(other.getValueClass()); |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hash(mName, mType); |
| } |
| }; |
| } |
| |
| /** |
| * @hide |
| * |
| * AudioMetadata is based on interfaces in order to allow multiple inheritance |
| * and maximum flexibility in implementation. |
| * |
| * Here, we provide a simple implementation of {@link Map} interface; |
| * Note that the Keys are not specific to this Map implementation. |
| * |
| * It is possible to require the keys to be of a certain class |
| * before allowing a set or get operation. |
| */ |
| public static class BaseMap implements AudioMetadataMap { |
| @Override |
| public <T> boolean containsKey(@NonNull Key<T> key) { |
| Pair<Key<?>, Object> valuePair = mHashMap.get(pairFromKey(key)); |
| return valuePair != null; |
| } |
| |
| @Override |
| @NonNull |
| public AudioMetadataMap dup() { |
| BaseMap map = new BaseMap(); |
| map.mHashMap.putAll(this.mHashMap); |
| return map; |
| } |
| |
| @Override |
| @Nullable |
| public <T> T get(@NonNull Key<T> key) { |
| Pair<Key<?>, Object> valuePair = mHashMap.get(pairFromKey(key)); |
| return (T) getValueFromValuePair(valuePair); |
| } |
| |
| @Override |
| @NonNull |
| public Set<Key<?>> keySet() { |
| HashSet<Key<?>> set = new HashSet(); |
| for (Pair<Key<?>, Object> pair : mHashMap.values()) { |
| set.add(pair.first); |
| } |
| return set; |
| } |
| |
| @Override |
| @Nullable |
| public <T> T remove(@NonNull Key<T> key) { |
| Pair<Key<?>, Object> valuePair = mHashMap.remove(pairFromKey(key)); |
| return (T) getValueFromValuePair(valuePair); |
| } |
| |
| @Override |
| @Nullable |
| public <T> T set(@NonNull Key<T> key, @NonNull T value) { |
| Objects.requireNonNull(value); |
| Pair<Key<?>, Object> valuePair = mHashMap |
| .put(pairFromKey(key), new Pair<Key<?>, Object>(key, value)); |
| return (T) getValueFromValuePair(valuePair); |
| } |
| |
| @Override |
| public int size() { |
| return mHashMap.size(); |
| } |
| |
| /** |
| * Return true if the object is a BaseMap and the content from two BaseMap are the same. |
| * Note: Need to override the equals functions of Key<T> for HashMap comparison. |
| */ |
| @Override |
| public boolean equals(Object obj) { |
| if (obj == this) { |
| return true; |
| } |
| if (!(obj instanceof BaseMap)) { |
| return false; |
| } |
| BaseMap other = (BaseMap) obj; |
| return mHashMap.equals(other.mHashMap); |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hash(mHashMap); |
| } |
| |
| /* |
| * Implementation specific. |
| * |
| * To store the value in the HashMap we need to convert the Key interface |
| * to a hashcode() / equals() compliant Pair. |
| */ |
| @NonNull |
| private static <T> Pair<String, Class<?>> pairFromKey(@NonNull Key<T> key) { |
| Objects.requireNonNull(key); |
| return new Pair<String, Class<?>>(key.getName(), key.getValueClass()); |
| } |
| |
| /* |
| * Implementation specific. |
| * |
| * We store in a Pair (valuePair) the key along with the Object value. |
| * This helper returns the Object value from the value pair. |
| */ |
| @Nullable |
| private static Object getValueFromValuePair(@Nullable Pair<Key<?>, Object> valuePair) { |
| if (valuePair == null) { |
| return null; |
| } |
| return valuePair.second; |
| } |
| |
| /* |
| * Implementation specific. |
| * |
| * We use a HashMap to back the AudioMetadata BaseMap object. |
| * This is not locked, so concurrent reads are permitted if all threads |
| * have a ReadMap; this is risky with a Map. |
| */ |
| private final HashMap<Pair<String, Class<?>>, Pair<Key<?>, Object>> mHashMap = |
| new HashMap(); |
| } |
| |
| // The audio metadata object type index should be kept the same as |
| // the ones in audio_utils::metadata::metadata_types |
| private static final int AUDIO_METADATA_OBJ_TYPE_NONE = 0; |
| private static final int AUDIO_METADATA_OBJ_TYPE_INT = 1; |
| private static final int AUDIO_METADATA_OBJ_TYPE_LONG = 2; |
| private static final int AUDIO_METADATA_OBJ_TYPE_FLOAT = 3; |
| private static final int AUDIO_METADATA_OBJ_TYPE_DOUBLE = 4; |
| private static final int AUDIO_METADATA_OBJ_TYPE_STRING = 5; |
| // BaseMap is corresponding to audio_utils::metadata::Data |
| private static final int AUDIO_METADATA_OBJ_TYPE_BASEMAP = 6; |
| |
| private static final Map<Class, Integer> AUDIO_METADATA_OBJ_TYPES = Map.of( |
| Integer.class, AUDIO_METADATA_OBJ_TYPE_INT, |
| Long.class, AUDIO_METADATA_OBJ_TYPE_LONG, |
| Float.class, AUDIO_METADATA_OBJ_TYPE_FLOAT, |
| Double.class, AUDIO_METADATA_OBJ_TYPE_DOUBLE, |
| String.class, AUDIO_METADATA_OBJ_TYPE_STRING, |
| BaseMap.class, AUDIO_METADATA_OBJ_TYPE_BASEMAP); |
| |
| private static final Charset AUDIO_METADATA_CHARSET = StandardCharsets.UTF_8; |
| |
| /** |
| * An auto growing byte buffer |
| */ |
| private static class AutoGrowByteBuffer { |
| private static final int INTEGER_BYTE_COUNT = Integer.SIZE / Byte.SIZE; |
| private static final int LONG_BYTE_COUNT = Long.SIZE / Byte.SIZE; |
| private static final int FLOAT_BYTE_COUNT = Float.SIZE / Byte.SIZE; |
| private static final int DOUBLE_BYTE_COUNT = Double.SIZE / Byte.SIZE; |
| |
| private ByteBuffer mBuffer; |
| |
| AutoGrowByteBuffer() { |
| this(1024); |
| } |
| |
| AutoGrowByteBuffer(@IntRange(from = 0) int initialCapacity) { |
| mBuffer = ByteBuffer.allocateDirect(initialCapacity); |
| } |
| |
| public ByteBuffer getRawByteBuffer() { |
| // Slice the buffer from 0 to position. |
| int limit = mBuffer.limit(); |
| int position = mBuffer.position(); |
| mBuffer.limit(position); |
| mBuffer.position(0); |
| ByteBuffer buffer = mBuffer.slice(); |
| |
| // Restore position and limit. |
| mBuffer.limit(limit); |
| mBuffer.position(position); |
| return buffer; |
| } |
| |
| public ByteOrder order() { |
| return mBuffer.order(); |
| } |
| |
| public int position() { |
| return mBuffer.position(); |
| } |
| |
| public AutoGrowByteBuffer position(int newPosition) { |
| mBuffer.position(newPosition); |
| return this; |
| } |
| |
| public AutoGrowByteBuffer order(ByteOrder order) { |
| mBuffer.order(order); |
| return this; |
| } |
| |
| public AutoGrowByteBuffer putInt(int value) { |
| ensureCapacity(INTEGER_BYTE_COUNT); |
| mBuffer.putInt(value); |
| return this; |
| } |
| |
| public AutoGrowByteBuffer putLong(long value) { |
| ensureCapacity(LONG_BYTE_COUNT); |
| mBuffer.putLong(value); |
| return this; |
| } |
| |
| public AutoGrowByteBuffer putFloat(float value) { |
| ensureCapacity(FLOAT_BYTE_COUNT); |
| mBuffer.putFloat(value); |
| return this; |
| } |
| |
| public AutoGrowByteBuffer putDouble(double value) { |
| ensureCapacity(DOUBLE_BYTE_COUNT); |
| mBuffer.putDouble(value); |
| return this; |
| } |
| |
| public AutoGrowByteBuffer put(byte[] src) { |
| ensureCapacity(src.length); |
| mBuffer.put(src); |
| return this; |
| } |
| |
| /** |
| * Ensures capacity to append at least <code>count</code> values. |
| */ |
| private void ensureCapacity(@IntRange int count) { |
| if (mBuffer.remaining() < count) { |
| int newCapacity = mBuffer.position() + count; |
| if (newCapacity > Integer.MAX_VALUE >> 1) { |
| throw new IllegalStateException( |
| "Item memory requirements too large: " + newCapacity); |
| } |
| newCapacity <<= 1; |
| ByteBuffer buffer = ByteBuffer.allocateDirect(newCapacity); |
| buffer.order(mBuffer.order()); |
| |
| // Copy data from old buffer to new buffer |
| mBuffer.flip(); |
| buffer.put(mBuffer); |
| |
| // Set buffer to new buffer |
| mBuffer = buffer; |
| } |
| } |
| } |
| |
| /** |
| * @hide |
| * Describes a unpacking/packing contract of type {@code T} out of a {@link ByteBuffer} |
| * |
| * @param <T> the type being unpack |
| */ |
| private interface DataPackage<T> { |
| /** |
| * Read an item from a {@link ByteBuffer}. |
| * |
| * The parceling format is assumed the same as the one described in |
| * audio_utils::Metadata.h. Copied here as a reference. |
| * All values are native endian order. |
| * |
| * Datum = { (type_size_t) Type (the type index from type_as_value<T>.) |
| * (datum_size_t) Size (size of datum, including the size field) |
| * (byte string) Payload<Type> |
| * } |
| * |
| * Primitive types: |
| * Payload<Type> = { bytes in native endian order } |
| * |
| * Vector, Map, Container types: |
| * Payload<Type> = { (index_size_t) number of elements |
| * (byte string) Payload<Element_Type> * number |
| * } |
| * |
| * Pair container types: |
| * Payload<Type> = { (byte string) Payload<first>, |
| * (byte string) Payload<second> |
| * } |
| * |
| * @param buffer the byte buffer to read from |
| * @return an object, which types is given type for {@link DataPackage} |
| * @throws BufferUnderflowException when there is no enough data remaining |
| * in the buffer for unpacking. |
| */ |
| @Nullable |
| T unpack(ByteBuffer buffer); |
| |
| /** |
| * Pack the item into a byte array. This is the reversed way of unpacking. |
| * |
| * @param output is the stream to which to write the data |
| * @param obj the item to pack |
| * @return true if packing successfully. Otherwise, return false. |
| */ |
| boolean pack(AutoGrowByteBuffer output, T obj); |
| |
| /** |
| * Return what kind of data is contained in the package. |
| */ |
| default Class getMyType() { |
| return (Class) ((ParameterizedType) getClass().getGenericInterfaces()[0]) |
| .getActualTypeArguments()[0]; |
| } |
| } |
| |
| /***************************************************************************************** |
| * Following class are common {@link DataPackage} implementations, which include types |
| * that are defined in audio_utils::metadata::metadata_types |
| * |
| * For Java |
| * int32_t corresponds to Integer |
| * int64_t corresponds to Long |
| * float corresponds to Float |
| * double corresponds to Double |
| * std::string corresponds to String |
| * Data corresponds to BaseMap |
| * Datum corresponds to Object |
| ****************************************************************************************/ |
| |
| private static final Map<Integer, DataPackage<?>> DATA_PACKAGES = Map.of( |
| AUDIO_METADATA_OBJ_TYPE_INT, new DataPackage<Integer>() { |
| @Override |
| @Nullable |
| public Integer unpack(ByteBuffer buffer) { |
| return buffer.getInt(); |
| } |
| |
| @Override |
| public boolean pack(AutoGrowByteBuffer output, Integer obj) { |
| output.putInt(obj); |
| return true; |
| } |
| }, |
| AUDIO_METADATA_OBJ_TYPE_LONG, new DataPackage<Long>() { |
| @Override |
| @Nullable |
| public Long unpack(ByteBuffer buffer) { |
| return buffer.getLong(); |
| } |
| |
| @Override |
| public boolean pack(AutoGrowByteBuffer output, Long obj) { |
| output.putLong(obj); |
| return true; |
| } |
| }, |
| AUDIO_METADATA_OBJ_TYPE_FLOAT, new DataPackage<Float>() { |
| @Override |
| @Nullable |
| public Float unpack(ByteBuffer buffer) { |
| return buffer.getFloat(); |
| } |
| |
| @Override |
| public boolean pack(AutoGrowByteBuffer output, Float obj) { |
| output.putFloat(obj); |
| return true; |
| } |
| }, |
| AUDIO_METADATA_OBJ_TYPE_DOUBLE, new DataPackage<Double>() { |
| @Override |
| @Nullable |
| public Double unpack(ByteBuffer buffer) { |
| return buffer.getDouble(); |
| } |
| |
| @Override |
| public boolean pack(AutoGrowByteBuffer output, Double obj) { |
| output.putDouble(obj); |
| return true; |
| } |
| }, |
| AUDIO_METADATA_OBJ_TYPE_STRING, new DataPackage<String>() { |
| @Override |
| @Nullable |
| public String unpack(ByteBuffer buffer) { |
| int dataSize = buffer.getInt(); |
| if (buffer.position() + dataSize > buffer.limit()) { |
| return null; |
| } |
| byte[] valueArr = new byte[dataSize]; |
| buffer.get(valueArr); |
| String value = new String(valueArr, AUDIO_METADATA_CHARSET); |
| return value; |
| } |
| |
| /** |
| * This is a reversed operation of unpack. It is needed to write the String |
| * at bytes encoded with AUDIO_METADATA_CHARSET. There should be an integer |
| * value representing the length of the bytes written before the bytes. |
| */ |
| @Override |
| public boolean pack(AutoGrowByteBuffer output, String obj) { |
| byte[] valueArr = obj.getBytes(AUDIO_METADATA_CHARSET); |
| output.putInt(valueArr.length); |
| output.put(valueArr); |
| return true; |
| } |
| }, |
| AUDIO_METADATA_OBJ_TYPE_BASEMAP, new BaseMapPackage()); |
| |
| // ObjectPackage is a special case that it is expected to unpack audio_utils::metadata::Datum, |
| // which contains data type and data size besides the payload for the data. |
| private static final ObjectPackage OBJECT_PACKAGE = new ObjectPackage(); |
| |
| private static class ObjectPackage implements DataPackage<Pair<Class, Object>> { |
| /** |
| * The {@link ObjectPackage} will unpack byte string for audio_utils::metadata::Datum. |
| * Since the Datum is a std::any, {@link Object} is used to carrying the data. The |
| * data type is stored in the data package header. In that case, a {@link Class} |
| * will also be returned to indicate the actual type for the object. |
| */ |
| @Override |
| @Nullable |
| public Pair<Class, Object> unpack(ByteBuffer buffer) { |
| int dataType = buffer.getInt(); |
| DataPackage dataPackage = DATA_PACKAGES.get(dataType); |
| if (dataPackage == null) { |
| Log.e(TAG, "Cannot find DataPackage for type:" + dataType); |
| return null; |
| } |
| int dataSize = buffer.getInt(); |
| int position = buffer.position(); |
| Object obj = dataPackage.unpack(buffer); |
| if (buffer.position() - position != dataSize) { |
| Log.e(TAG, "Broken data package"); |
| return null; |
| } |
| return new Pair<Class, Object>(dataPackage.getMyType(), obj); |
| } |
| |
| @Override |
| public boolean pack(AutoGrowByteBuffer output, Pair<Class, Object> obj) { |
| final Integer dataType = AUDIO_METADATA_OBJ_TYPES.get(obj.first); |
| if (dataType == null) { |
| Log.e(TAG, "Cannot find data type for " + obj.first); |
| return false; |
| } |
| DataPackage dataPackage = DATA_PACKAGES.get(dataType); |
| if (dataPackage == null) { |
| Log.e(TAG, "Cannot find DataPackage for type:" + dataType); |
| return false; |
| } |
| output.putInt(dataType); |
| int position = output.position(); // Keep current position. |
| output.putInt(0); // Keep a place for the size of payload. |
| int payloadIdx = output.position(); |
| if (!dataPackage.pack(output, obj.second)) { |
| Log.i(TAG, "Failed to pack object: " + obj.second); |
| return false; |
| } |
| // Put the actual payload size. |
| int currentPosition = output.position(); |
| output.position(position); |
| output.putInt(currentPosition - payloadIdx); |
| output.position(currentPosition); |
| return true; |
| } |
| } |
| |
| /** |
| * BaseMap will be corresponding to audio_utils::metadata::Data. |
| */ |
| private static class BaseMapPackage implements DataPackage<BaseMap> { |
| @Override |
| @Nullable |
| public BaseMap unpack(ByteBuffer buffer) { |
| BaseMap ret = new BaseMap(); |
| int mapSize = buffer.getInt(); |
| DataPackage<String> strDataPackage = |
| (DataPackage<String>) DATA_PACKAGES.get(AUDIO_METADATA_OBJ_TYPE_STRING); |
| if (strDataPackage == null) { |
| Log.e(TAG, "Cannot find DataPackage for String"); |
| return null; |
| } |
| for (int i = 0; i < mapSize; i++) { |
| String key = strDataPackage.unpack(buffer); |
| if (key == null) { |
| Log.e(TAG, "Failed to unpack key for map"); |
| return null; |
| } |
| Pair<Class, Object> value = OBJECT_PACKAGE.unpack(buffer); |
| if (value == null) { |
| Log.e(TAG, "Failed to unpack value for map"); |
| return null; |
| } |
| |
| // Special handling of KEY_ATMOS_PRESENT. |
| if (key.equals(Format.KEY_HAS_ATMOS.getName()) |
| && value.first == Format.KEY_HAS_ATMOS.getValueClass()) { |
| ret.set(Format.KEY_ATMOS_PRESENT, |
| (Boolean) ((int) value.second != 0)); // Translate Integer to Boolean |
| continue; // Should we store both keys in the java table? |
| } |
| |
| ret.set(createKey(key, value.first), value.first.cast(value.second)); |
| } |
| return ret; |
| } |
| |
| @Override |
| public boolean pack(AutoGrowByteBuffer output, BaseMap obj) { |
| output.putInt(obj.size()); |
| DataPackage<String> strDataPackage = |
| (DataPackage<String>) DATA_PACKAGES.get(AUDIO_METADATA_OBJ_TYPE_STRING); |
| if (strDataPackage == null) { |
| Log.e(TAG, "Cannot find DataPackage for String"); |
| return false; |
| } |
| for (Key<?> key : obj.keySet()) { |
| Object value = obj.get(key); |
| |
| // Special handling of KEY_ATMOS_PRESENT. |
| if (key == Format.KEY_ATMOS_PRESENT) { |
| key = Format.KEY_HAS_ATMOS; |
| value = (Integer) ((boolean) value ? 1 : 0); // Translate Boolean to Integer |
| } |
| |
| if (!strDataPackage.pack(output, key.getName())) { |
| Log.i(TAG, "Failed to pack key: " + key.getName()); |
| return false; |
| } |
| if (!OBJECT_PACKAGE.pack(output, new Pair<>(key.getValueClass(), value))) { |
| Log.i(TAG, "Failed to pack value: " + obj.get(key)); |
| return false; |
| } |
| } |
| return true; |
| } |
| } |
| |
| /** |
| * @hide |
| * Extract a {@link BaseMap} from a given {@link ByteBuffer} |
| * @param buffer is a byte string that contains information to unpack. |
| * @return a {@link BaseMap} object if extracting successfully from given byte buffer. |
| * Otherwise, returns {@code null}. |
| */ |
| @Nullable |
| public static BaseMap fromByteBuffer(ByteBuffer buffer) { |
| DataPackage dataPackage = DATA_PACKAGES.get(AUDIO_METADATA_OBJ_TYPE_BASEMAP); |
| if (dataPackage == null) { |
| Log.e(TAG, "Cannot find DataPackage for BaseMap"); |
| return null; |
| } |
| try { |
| return (BaseMap) dataPackage.unpack(buffer); |
| } catch (BufferUnderflowException e) { |
| Log.e(TAG, "No enough data to unpack"); |
| } |
| return null; |
| } |
| |
| /** |
| * @hide |
| * Pack a {link BaseMap} to a {@link ByteBuffer} |
| * @param data is the object for packing |
| * @param order is the byte order |
| * @return a {@link ByteBuffer} if successfully packing the data. |
| * Otherwise, returns {@code null}; |
| */ |
| @Nullable |
| public static ByteBuffer toByteBuffer(BaseMap data, ByteOrder order) { |
| DataPackage dataPackage = DATA_PACKAGES.get(AUDIO_METADATA_OBJ_TYPE_BASEMAP); |
| if (dataPackage == null) { |
| Log.e(TAG, "Cannot find DataPackage for BaseMap"); |
| return null; |
| } |
| AutoGrowByteBuffer output = new AutoGrowByteBuffer(); |
| output.order(order); |
| if (dataPackage.pack(output, data)) { |
| return output.getRawByteBuffer(); |
| } |
| return null; |
| } |
| |
| // Delete the constructor as there is nothing to implement here. |
| private AudioMetadata() {} |
| } |