All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.android.media.AudioMetadata Maven / Gradle / Ivy

Go to download

A library jar that provides APIs for Applications written for the Google Android Platform.

There is a newer version: 15-robolectric-12650502
Show newest version
/*
 * 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.
     *
     * 

The presence of this {@code Key} interface on an object allows * it to reference metadata in the Audio Framework.

* *

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".

* * @param 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 { /** * 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 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 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 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 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 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 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 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 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 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 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 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 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 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 The type of value. * @return a new key interface. * * Creating keys is currently only allowed by the Framework. * @hide */ @NonNull public static Key createKey(@NonNull String name, @NonNull Class type) { // Implementation specific. return new Key() { private final String mName = name; private final Class mType = type; @Override @NonNull public String getName() { return mName; } @Override @NonNull public Class 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 boolean containsKey(@NonNull Key key) { Pair, 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 get(@NonNull Key key) { Pair, Object> valuePair = mHashMap.get(pairFromKey(key)); return (T) getValueFromValuePair(valuePair); } @Override @NonNull public Set> keySet() { HashSet> set = new HashSet(); for (Pair, Object> pair : mHashMap.values()) { set.add(pair.first); } return set; } @Override @Nullable public T remove(@NonNull Key key) { Pair, Object> valuePair = mHashMap.remove(pairFromKey(key)); return (T) getValueFromValuePair(valuePair); } @Override @Nullable public T set(@NonNull Key key, @NonNull T value) { Objects.requireNonNull(value); Pair, Object> valuePair = mHashMap .put(pairFromKey(key), new Pair, 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 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 Pair> pairFromKey(@NonNull Key key) { Objects.requireNonNull(key); return new Pair>(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, 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, 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 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 count 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 the type being unpack */ private interface DataPackage { /** * 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.) * (datum_size_t) Size (size of datum, including the size field) * (byte string) Payload * } * * Primitive types: * Payload = { bytes in native endian order } * * Vector, Map, Container types: * Payload = { (index_size_t) number of elements * (byte string) Payload * number * } * * Pair container types: * Payload = { (byte string) Payload, * (byte string) Payload * } * * @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> DATA_PACKAGES = new HashMap<>() {{ put(AUDIO_METADATA_OBJ_TYPE_INT, new DataPackage() { @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() { @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() { @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() { @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() { @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> { /** * 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 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(dataPackage.getMyType(), obj); } @Override public boolean pack(AutoGrowByteBuffer output, Pair 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 { @Override @Nullable public BaseMap unpack(ByteBuffer buffer) { BaseMap ret = new BaseMap(); int mapSize = buffer.getInt(); DataPackage strDataPackage = (DataPackage) 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 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 strDataPackage = (DataPackage) 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() {} }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy