/* * Copyright (C) 2022 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.bluetooth; import static android.bluetooth.BluetoothLeAudioCodecConfig.FRAME_DURATION_10000; import static android.bluetooth.BluetoothLeAudioCodecConfig.FRAME_DURATION_7500; import static android.bluetooth.BluetoothLeAudioCodecConfig.FRAME_DURATION_NONE; import static android.bluetooth.BluetoothLeAudioCodecConfig.FrameDuration; import static android.bluetooth.BluetoothLeAudioCodecConfig.SAMPLE_RATE_16000; import static android.bluetooth.BluetoothLeAudioCodecConfig.SAMPLE_RATE_24000; import static android.bluetooth.BluetoothLeAudioCodecConfig.SAMPLE_RATE_32000; import static android.bluetooth.BluetoothLeAudioCodecConfig.SAMPLE_RATE_44100; import static android.bluetooth.BluetoothLeAudioCodecConfig.SAMPLE_RATE_48000; import static android.bluetooth.BluetoothLeAudioCodecConfig.SAMPLE_RATE_8000; import static android.bluetooth.BluetoothLeAudioCodecConfig.SAMPLE_RATE_NONE; import static android.bluetooth.BluetoothLeAudioCodecConfig.SampleRate; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.bluetooth.BluetoothLeAudioCodecConfig.FrameDuration; import android.bluetooth.BluetoothLeAudioCodecConfig.SampleRate; import android.bluetooth.BluetoothUtils.TypeValueEntry; import android.os.Parcel; import android.os.Parcelable; import com.android.bluetooth.flags.Flags; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; /** * A class representing the codec specific config metadata information defined in the Basic Audio * Profile. * * @hide */ @SystemApi public final class BluetoothLeAudioCodecConfigMetadata implements Parcelable { private static final int SAMPLING_FREQUENCY_TYPE = 0x01; private static final int FRAME_DURATION_TYPE = 0x02; private static final int AUDIO_CHANNEL_LOCATION_TYPE = 0x03; private static final int OCTETS_PER_FRAME_TYPE = 0x04; private final long mAudioLocation; private final @SampleRate int mSampleRate; private final @FrameDuration int mFrameDuration; private final int mOctetsPerFrame; private final byte[] mRawMetadata; /** Audio codec sampling frequency from metadata. */ private static final int CONFIG_SAMPLING_FREQUENCY_UNKNOWN = 0; private static final int CONFIG_SAMPLING_FREQUENCY_8000 = 0x01; private static final int CONFIG_SAMPLING_FREQUENCY_16000 = 0x03; private static final int CONFIG_SAMPLING_FREQUENCY_24000 = 0x05; private static final int CONFIG_SAMPLING_FREQUENCY_32000 = 0x06; private static final int CONFIG_SAMPLING_FREQUENCY_44100 = 0x07; private static final int CONFIG_SAMPLING_FREQUENCY_48000 = 0x08; private static final int CONFIG_SAMPLING_FREQUENCY_11025 = 0x09; private static final int CONFIG_SAMPLING_FREQUENCY_22050 = 0x0a; private static final int CONFIG_SAMPLING_FREQUENCY_88200 = 0x0b; private static final int CONFIG_SAMPLING_FREQUENCY_96000 = 0x0c; private static final int CONFIG_SAMPLING_FREQUENCY_176400 = 0x0d; private static final int CONFIG_SAMPLING_FREQUENCY_192000 = 0x0e; private static final int CONFIG_SAMPLING_FREQUENCY_384000 = 0x0f; /** Audio codec config frame duration from metadata. */ private static final int CONFIG_FRAME_DURATION_UNKNOWN = -1; private static final int CONFIG_FRAME_DURATION_7500 = 0x00; private static final int CONFIG_FRAME_DURATION_10000 = 0x01; private BluetoothLeAudioCodecConfigMetadata( long audioLocation, @SampleRate int sampleRate, @FrameDuration int frameDuration, int octetsPerFrame, byte[] rawMetadata) { mAudioLocation = audioLocation; mSampleRate = sampleRate; mFrameDuration = frameDuration; mOctetsPerFrame = octetsPerFrame; mRawMetadata = rawMetadata; } @Override public boolean equals(@Nullable Object o) { if (o != null && o instanceof BluetoothLeAudioCodecConfigMetadata) { final BluetoothLeAudioCodecConfigMetadata oth = (BluetoothLeAudioCodecConfigMetadata) o; return mAudioLocation == oth.getAudioLocation() && mSampleRate == oth.getSampleRate() && mFrameDuration == oth.getFrameDuration() && mOctetsPerFrame == oth.getOctetsPerFrame() && Arrays.equals(mRawMetadata, oth.getRawMetadata()); } return false; } @Override public int hashCode() { return Objects.hash( mAudioLocation, mSampleRate, mFrameDuration, mOctetsPerFrame, Arrays.hashCode(mRawMetadata)); } @Override public String toString() { return "BluetoothLeAudioCodecConfigMetadata{" + ("audioLocation=" + mAudioLocation) + (", sampleRate=" + mSampleRate) + (", frameDuration=" + mFrameDuration) + (", octetsPerFrame=" + mOctetsPerFrame) + (", rawMetadata=" + Arrays.toString(mRawMetadata)) + '}'; } /** * Get the audio location information as defined in the Generic Audio section of Bluetooth * Assigned numbers. * * @return configured audio location, -1 if this metadata does not exist * @hide */ @SystemApi public long getAudioLocation() { return mAudioLocation; } /** * Get the audio sample rate information as defined in the Generic Audio section of Bluetooth * Assigned numbers 6.12.4.1 Supported_Sampling_Frequencies. * *

Internally this is converted from Sampling_Frequency values as defined in 6.12.5.1 * * @return configured sample rate from meta data, {@link * BluetoothLeAudioCodecConfig#SAMPLE_RATE_NONE} if this metadata does not exist * @hide */ @SystemApi public @SampleRate int getSampleRate() { return mSampleRate; } /** * Get the audio frame duration information as defined in the Generic Audio section of Bluetooth * Assigned numbers 6.12.5.2 Frame_Duration. * *

Internally this is converted from Frame_Durations values as defined in 6.12.4.2 * * @return configured frame duration from meta data, {@link * BluetoothLeAudioCodecConfig#FRAME_DURATION_NONE} if this metadata does not exist * @hide */ @SystemApi public @FrameDuration int getFrameDuration() { return mFrameDuration; } /** * Get the audio octets per frame information as defined in the Generic Audio section of * Bluetooth Assigned numbers. * * @return configured octets per frame from meta data 0 if this metadata does not exist * @hide */ @SystemApi public int getOctetsPerFrame() { return mOctetsPerFrame; } /** * Get the raw bytes of stream metadata in Bluetooth LTV format. * *

Bluetooth LTV format for stream metadata is defined in the Generic Audio section of Bluetooth Assigned * Numbers, including metadata that was not covered by the getter methods in this class. * * @return raw bytes of stream metadata in Bluetooth LTV format * @hide */ @SystemApi public @NonNull byte[] getRawMetadata() { return mRawMetadata; } /** * {@inheritDoc} * * @hide */ @Override public int describeContents() { return 0; } /** * {@inheritDoc} * * @hide */ @Override public void writeToParcel(Parcel out, int flags) { out.writeLong(mAudioLocation); if (mRawMetadata != null) { out.writeInt(mRawMetadata.length); out.writeByteArray(mRawMetadata); } else { out.writeInt(-1); } out.writeInt(mSampleRate); out.writeInt(mFrameDuration); out.writeInt(mOctetsPerFrame); } /** * A {@link Parcelable.Creator} to create {@link BluetoothLeAudioCodecConfigMetadata} from * parcel. * * @hide */ @SystemApi @NonNull public static final Creator CREATOR = new Creator<>() { public @NonNull BluetoothLeAudioCodecConfigMetadata createFromParcel( @NonNull Parcel in) { long audioLocation = in.readLong(); int rawMetadataLen = in.readInt(); byte[] rawMetadata; if (rawMetadataLen != -1) { rawMetadata = new byte[rawMetadataLen]; in.readByteArray(rawMetadata); } else { rawMetadata = new byte[0]; } int sampleRate = in.readInt(); int frameDuration = in.readInt(); int octetsPerFrame = in.readInt(); return new BluetoothLeAudioCodecConfigMetadata( audioLocation, sampleRate, frameDuration, octetsPerFrame, rawMetadata); } public @NonNull BluetoothLeAudioCodecConfigMetadata[] newArray(int size) { return new BluetoothLeAudioCodecConfigMetadata[size]; } }; /** * Construct a {@link BluetoothLeAudioCodecConfigMetadata} from raw bytes. * *

The byte array will be parsed and values for each getter will be populated * *

Raw metadata cannot be set using builder in order to maintain raw bytes and getter value * consistency * * @param rawBytes raw bytes of stream metadata in Bluetooth LTV format * @return parsed {@link BluetoothLeAudioCodecConfigMetadata} object * @throws IllegalArgumentException if rawBytes is null or when the raw bytes cannot * be parsed to build the object * @hide */ @SystemApi @NonNull public static BluetoothLeAudioCodecConfigMetadata fromRawBytes(@NonNull byte[] rawBytes) { if (rawBytes == null) { throw new IllegalArgumentException("Raw bytes cannot be null"); } List entries = BluetoothUtils.parseLengthTypeValueBytes(rawBytes); if (rawBytes.length > 0 && rawBytes[0] > 0 && entries.isEmpty()) { throw new IllegalArgumentException( "No LTV entries are found from rawBytes of size " + rawBytes.length); } long audioLocation = 0; int samplingFrequency = CONFIG_SAMPLING_FREQUENCY_UNKNOWN; int frameDuration = CONFIG_FRAME_DURATION_UNKNOWN; int octetsPerFrame = 0; for (TypeValueEntry entry : entries) { if (entry.getType() == AUDIO_CHANNEL_LOCATION_TYPE) { byte[] bytes = entry.getValue(); // Get unsigned uint32_t to long audioLocation = ((bytes[0] & 0xFF) << 0) | ((bytes[1] & 0xFF) << 8) | ((bytes[2] & 0xFF) << 16) | ((long) (bytes[3] & 0xFF) << 24); } else if (entry.getType() == SAMPLING_FREQUENCY_TYPE) { byte[] bytes = entry.getValue(); // Get one byte for sampling frequency in value samplingFrequency = (int) (bytes[0] & 0xFF); } else if (entry.getType() == FRAME_DURATION_TYPE) { byte[] bytes = entry.getValue(); // Get one byte for frame duration in value frameDuration = (int) (bytes[0] & 0xFF); } else if (entry.getType() == OCTETS_PER_FRAME_TYPE) { byte[] bytes = entry.getValue(); // Get two bytes for octets per frame to int octetsPerFrame = ((bytes[0] & 0xFF) << 0) | ((int) (bytes[1] & 0xFF) << 8); } } return new BluetoothLeAudioCodecConfigMetadata( audioLocation, convertToSampleRateBitset(samplingFrequency), convertToFrameDurationBitset(frameDuration), octetsPerFrame, rawBytes); } /** * Builder for {@link BluetoothLeAudioCodecConfigMetadata}. * * @hide */ @SystemApi public static final class Builder { private long mAudioLocation = 0; private int mSampleRate = SAMPLE_RATE_NONE; private int mFrameDuration = FRAME_DURATION_NONE; private int mOctetsPerFrame = 0; private byte[] mRawMetadata = null; /** * Create an empty builder. * * @hide */ @SystemApi public Builder() {} /** * Create a builder with copies of information from original object. * * @param original original object * @hide */ @SystemApi public Builder(@NonNull BluetoothLeAudioCodecConfigMetadata original) { mAudioLocation = original.getAudioLocation(); mSampleRate = original.getSampleRate(); mFrameDuration = original.getFrameDuration(); mOctetsPerFrame = original.getOctetsPerFrame(); mRawMetadata = original.getRawMetadata(); } /** * Set the audio location information as defined in the Generic Audio section of Bluetooth * Assigned numbers. * * @param audioLocation configured audio location, -1 if does not exist * @return this builder * @hide */ @SystemApi @NonNull public Builder setAudioLocation(long audioLocation) { mAudioLocation = audioLocation; return this; } /** * Set the audio sample rate information as defined in the Generic Audio section of * Bluetooth Assigned 6.12.4.1 Supported_Sampling_Frequencies. * *

Internally this will be converted to Sampling_Frequency values as defined in 6.12.5.1 * * @param sampleRate configured sample rate in meta data * @return this builder * @throws IllegalArgumentException if sample rate is invalid value * @hide */ @SystemApi @NonNull public Builder setSampleRate(@SampleRate int sampleRate) { if (sampleRate != SAMPLE_RATE_NONE && sampleRate != SAMPLE_RATE_8000 && sampleRate != SAMPLE_RATE_16000 && sampleRate != SAMPLE_RATE_24000 && sampleRate != SAMPLE_RATE_32000 && sampleRate != SAMPLE_RATE_44100 && sampleRate != SAMPLE_RATE_48000) { if (Flags.leaudioAddSamplingFrequencies()) { if (sampleRate != BluetoothLeAudioCodecConfig.SAMPLE_RATE_11025 && sampleRate != BluetoothLeAudioCodecConfig.SAMPLE_RATE_22050 && sampleRate != BluetoothLeAudioCodecConfig.SAMPLE_RATE_88200 && sampleRate != BluetoothLeAudioCodecConfig.SAMPLE_RATE_96000 && sampleRate != BluetoothLeAudioCodecConfig.SAMPLE_RATE_176400 && sampleRate != BluetoothLeAudioCodecConfig.SAMPLE_RATE_192000 && sampleRate != BluetoothLeAudioCodecConfig.SAMPLE_RATE_384000) { throw new IllegalArgumentException("Invalid sample rate " + sampleRate); } } else { throw new IllegalArgumentException("Invalid sample rate " + sampleRate); } } mSampleRate = sampleRate; return this; } /** * Set the audio frame duration information as defined in the Generic Audio section of * Bluetooth Assigned numbers 6.12.5.2 Frame_Duration. * *

Internally this will be converted to Frame_Durations values as defined in 6.12.4.2 * * @param frameDuration configured frame duration in meta data * @return this builder * @throws IllegalArgumentException if frameDuration is invalid value * @hide */ @SystemApi @NonNull public Builder setFrameDuration(@FrameDuration int frameDuration) { if (frameDuration != FRAME_DURATION_NONE && frameDuration != FRAME_DURATION_7500 && frameDuration != FRAME_DURATION_10000) { throw new IllegalArgumentException("Invalid frame duration " + frameDuration); } mFrameDuration = frameDuration; return this; } /** * Set the audio octets per frame information as defined in the Generic Audio section of * Bluetooth Assigned numbers. * * @param octetsPerFrame configured octets per frame in meta data * @return this builder * @throws IllegalArgumentException if octetsPerFrame is invalid value * @hide */ @SystemApi @NonNull public Builder setOctetsPerFrame(int octetsPerFrame) { if (octetsPerFrame < 0) { throw new IllegalArgumentException("Invalid octetsPerFrame " + octetsPerFrame); } mOctetsPerFrame = octetsPerFrame; return this; } /** * Build {@link BluetoothLeAudioCodecConfigMetadata}. * * @return constructed {@link BluetoothLeAudioCodecConfigMetadata} * @throws IllegalArgumentException if the object cannot be built * @hide */ @SystemApi public @NonNull BluetoothLeAudioCodecConfigMetadata build() { List entries = new ArrayList<>(); if (mRawMetadata != null) { entries = BluetoothUtils.parseLengthTypeValueBytes(mRawMetadata); if (mRawMetadata.length > 0 && mRawMetadata[0] > 0 && entries.isEmpty()) { throw new IllegalArgumentException( "No LTV entries are found from rawBytes of" + " size " + mRawMetadata.length + " please check the original object" + " passed to Builder's copy constructor"); } } if (mSampleRate != SAMPLE_RATE_NONE) { int samplingFrequency = convertToSamplingFrequencyValue(mSampleRate); entries.removeIf(entry -> entry.getType() == SAMPLING_FREQUENCY_TYPE); entries.add( new TypeValueEntry( SAMPLING_FREQUENCY_TYPE, ByteBuffer.allocate(1) .put((byte) (samplingFrequency & 0xFF)) .array())); } if (mFrameDuration != FRAME_DURATION_NONE) { int frameDuration = convertToFrameDurationValue(mFrameDuration); entries.removeIf(entry -> entry.getType() == FRAME_DURATION_TYPE); entries.add( new TypeValueEntry( FRAME_DURATION_TYPE, ByteBuffer.allocate(1).put((byte) (frameDuration & 0xFF)).array())); } if (mAudioLocation != -1) { entries.removeIf(entry -> entry.getType() == AUDIO_CHANNEL_LOCATION_TYPE); entries.add( new TypeValueEntry( AUDIO_CHANNEL_LOCATION_TYPE, ByteBuffer.allocate(4) .putInt((int) (mAudioLocation & 0xFFFFFFFF)) .array())); } if (mOctetsPerFrame != 0) { entries.removeIf(entry -> entry.getType() == OCTETS_PER_FRAME_TYPE); entries.add( new TypeValueEntry( OCTETS_PER_FRAME_TYPE, ByteBuffer.allocate(2) .putShort((short) (mOctetsPerFrame & 0xFFFF)) .array())); } byte[] rawBytes = BluetoothUtils.serializeTypeValue(entries); if (rawBytes == null) { throw new IllegalArgumentException("Failed to serialize entries to bytes"); } return new BluetoothLeAudioCodecConfigMetadata( mAudioLocation, mSampleRate, mFrameDuration, mOctetsPerFrame, rawBytes); } } private static int convertToSampleRateBitset(int samplingFrequencyValue) { switch (samplingFrequencyValue) { case CONFIG_SAMPLING_FREQUENCY_8000: return SAMPLE_RATE_8000; case CONFIG_SAMPLING_FREQUENCY_16000: return SAMPLE_RATE_16000; case CONFIG_SAMPLING_FREQUENCY_24000: return SAMPLE_RATE_24000; case CONFIG_SAMPLING_FREQUENCY_32000: return SAMPLE_RATE_32000; case CONFIG_SAMPLING_FREQUENCY_44100: return SAMPLE_RATE_44100; case CONFIG_SAMPLING_FREQUENCY_48000: return SAMPLE_RATE_48000; default: if (Flags.leaudioAddSamplingFrequencies()) { switch (samplingFrequencyValue) { case CONFIG_SAMPLING_FREQUENCY_11025: return BluetoothLeAudioCodecConfig.SAMPLE_RATE_11025; case CONFIG_SAMPLING_FREQUENCY_22050: return BluetoothLeAudioCodecConfig.SAMPLE_RATE_22050; case CONFIG_SAMPLING_FREQUENCY_88200: return BluetoothLeAudioCodecConfig.SAMPLE_RATE_88200; case CONFIG_SAMPLING_FREQUENCY_96000: return BluetoothLeAudioCodecConfig.SAMPLE_RATE_96000; case CONFIG_SAMPLING_FREQUENCY_176400: return BluetoothLeAudioCodecConfig.SAMPLE_RATE_176400; case CONFIG_SAMPLING_FREQUENCY_192000: return BluetoothLeAudioCodecConfig.SAMPLE_RATE_192000; case CONFIG_SAMPLING_FREQUENCY_384000: return BluetoothLeAudioCodecConfig.SAMPLE_RATE_384000; } } return SAMPLE_RATE_NONE; } } private static int convertToSamplingFrequencyValue(int sampleRateBitSet) { switch (sampleRateBitSet) { case SAMPLE_RATE_8000: return CONFIG_SAMPLING_FREQUENCY_8000; case SAMPLE_RATE_16000: return CONFIG_SAMPLING_FREQUENCY_16000; case SAMPLE_RATE_24000: return CONFIG_SAMPLING_FREQUENCY_24000; case SAMPLE_RATE_32000: return CONFIG_SAMPLING_FREQUENCY_32000; case SAMPLE_RATE_44100: return CONFIG_SAMPLING_FREQUENCY_44100; case SAMPLE_RATE_48000: return CONFIG_SAMPLING_FREQUENCY_48000; default: if (Flags.leaudioAddSamplingFrequencies()) { switch (sampleRateBitSet) { case BluetoothLeAudioCodecConfig.SAMPLE_RATE_11025: return CONFIG_SAMPLING_FREQUENCY_11025; case BluetoothLeAudioCodecConfig.SAMPLE_RATE_22050: return CONFIG_SAMPLING_FREQUENCY_22050; case BluetoothLeAudioCodecConfig.SAMPLE_RATE_88200: return CONFIG_SAMPLING_FREQUENCY_88200; case BluetoothLeAudioCodecConfig.SAMPLE_RATE_96000: return CONFIG_SAMPLING_FREQUENCY_96000; case BluetoothLeAudioCodecConfig.SAMPLE_RATE_176400: return CONFIG_SAMPLING_FREQUENCY_176400; case BluetoothLeAudioCodecConfig.SAMPLE_RATE_192000: return CONFIG_SAMPLING_FREQUENCY_192000; case BluetoothLeAudioCodecConfig.SAMPLE_RATE_384000: return CONFIG_SAMPLING_FREQUENCY_384000; } } return CONFIG_SAMPLING_FREQUENCY_UNKNOWN; } } private static int convertToFrameDurationBitset(int frameDurationValue) { switch (frameDurationValue) { case CONFIG_FRAME_DURATION_7500: return FRAME_DURATION_7500; case CONFIG_FRAME_DURATION_10000: return FRAME_DURATION_10000; default: return FRAME_DURATION_NONE; } } private static int convertToFrameDurationValue(int frameDurationBitset) { switch (frameDurationBitset) { case FRAME_DURATION_7500: return CONFIG_FRAME_DURATION_7500; case FRAME_DURATION_10000: return CONFIG_FRAME_DURATION_10000; default: return CONFIG_FRAME_DURATION_UNKNOWN; } } }