|  | /* | 
|  | * 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.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.ContentClassifier | 
|  | * One of {@link AudioPresentation#CONTENT_UNKNOWN}, | 
|  | *     {@link AudioPresentation#CONTENT_MAIN}, | 
|  | *     {@link AudioPresentation#CONTENT_MUSIC_AND_EFFECTS}, | 
|  | *     {@link AudioPresentation#CONTENT_VISUALLY_IMPAIRED}, | 
|  | *     {@link AudioPresentation#CONTENT_HEARING_IMPAIRED}, | 
|  | *     {@link AudioPresentation#CONTENT_DIALOG}, | 
|  | *     {@link AudioPresentation#CONTENT_COMMENTARY}, | 
|  | *     {@link AudioPresentation#CONTENT_EMERGENCY}, | 
|  | *     {@link 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 HashMap<Class, Integer> AUDIO_METADATA_OBJ_TYPES = new HashMap<>() {{ | 
|  | put(Integer.class, AUDIO_METADATA_OBJ_TYPE_INT); | 
|  | put(Long.class, AUDIO_METADATA_OBJ_TYPE_LONG); | 
|  | put(Float.class, AUDIO_METADATA_OBJ_TYPE_FLOAT); | 
|  | put(Double.class, AUDIO_METADATA_OBJ_TYPE_DOUBLE); | 
|  | put(String.class, AUDIO_METADATA_OBJ_TYPE_STRING); | 
|  | put(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 HashMap<Integer, DataPackage<?>> DATA_PACKAGES = new HashMap<>() {{ | 
|  | put(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; | 
|  | } | 
|  | }); | 
|  | put(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; | 
|  | } | 
|  | }); | 
|  | put(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; | 
|  | } | 
|  | }); | 
|  | put(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; | 
|  | } | 
|  | }); | 
|  | put(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; | 
|  | } | 
|  | }); | 
|  | put(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() {} | 
|  | } |