• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.bluetooth.bass_client;
18 
19 import android.util.Log;
20 import android.util.Pair;
21 
22 import java.nio.ByteBuffer;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.List;
26 import java.util.Locale;
27 import java.util.MissingResourceException;
28 
29 /** Helper class to parse the Broadcast Announcement BASE data */
BaseData( BaseInformation levelOne, List<BaseInformation> levelTwo, List<BaseInformation> levelThree, int numberOfBISIndices)30 record BaseData(
31         BaseInformation levelOne,
32         List<BaseInformation> levelTwo,
33         List<BaseInformation> levelThree,
34         int numberOfBISIndices) {
35     private static final String TAG = BassClientService.TAG + "." + BaseData.class.getSimpleName();
36 
37     private static final int METADATA_LEVEL1 = 1;
38     private static final int METADATA_LEVEL2 = 2;
39     private static final int METADATA_LEVEL3 = 3;
40     private static final int METADATA_PRESENTATION_DELAY_LENGTH = 3;
41     private static final int METADATA_CODEC_LENGTH = 5;
42     private static final int CODEC_CONFIGURATION_SAMPLE_RATE_TYPE = 0x01;
43     private static final int CODEC_CONFIGURATION_FRAME_DURATION_TYPE = 0x02;
44     private static final int CODEC_CONFIGURATION_CHANNEL_ALLOCATION_TYPE = 0x03;
45     private static final int CODEC_CONFIGURATION_OCTETS_PER_FRAME_TYPE = 0x04;
46     private static final int METADATA_LANGUAGE_TYPE = 0x04;
47     private static final int CODEC_AUDIO_LOCATION_FRONT_LEFT = 0x01000000;
48     private static final int CODEC_AUDIO_LOCATION_FRONT_RIGHT = 0x02000000;
49     private static final int CODEC_AUDIO_SAMPLE_RATE_8K = 0x01;
50     private static final int CODEC_AUDIO_SAMPLE_RATE_16K = 0x03;
51     private static final int CODEC_AUDIO_SAMPLE_RATE_24K = 0x05;
52     private static final int CODEC_AUDIO_SAMPLE_RATE_32K = 0x06;
53     private static final int CODEC_AUDIO_SAMPLE_RATE_44P1K = 0x07;
54     private static final int CODEC_AUDIO_SAMPLE_RATE_48K = 0x08;
55     private static final int CODEC_AUDIO_FRAME_DURATION_7P5MS = 0x00;
56     private static final int CODEC_AUDIO_FRAME_DURATION_10MS = 0x01;
57 
58     static class BaseInformation {
59         final byte[] mPresentationDelay = new byte[3];
60         final byte[] mCodecId = new byte[5];
61         int mCodecConfigLength;
62         byte[] mCodecConfigInfo;
63         int mMetaDataLength;
64         byte[] mMetaData;
65         byte mNumSubGroups;
66         byte mIndex;
67         int mSubGroupId;
68         int mLevel;
69 
70         BaseInformation() {
71             mCodecConfigLength = 0;
72             mCodecConfigInfo = new byte[0];
73             mMetaDataLength = 0;
74             mMetaData = new byte[0];
75             mNumSubGroups = 0;
76             mIndex = (byte) 0xFF;
77             mLevel = 0;
78             log("BaseInformation is Initialized");
79         }
80 
81         void print() {
82             log("**BEGIN: Base Information**");
83             log("**Level: " + mLevel + "***");
84             if (mLevel == 1) {
85                 log("mPresentationDelay: " + Arrays.toString(mPresentationDelay));
86             }
87             if (mLevel == 2) {
88                 log("mCodecId: " + Arrays.toString(mCodecId));
89             }
90             if (mLevel == 2 || mLevel == 3) {
91                 log("mCodecConfigLength: " + mCodecConfigLength);
92                 log("mSubGroupId: " + mSubGroupId);
93             }
94             if (mCodecConfigLength != 0) {
95                 log("mCodecConfigInfo: " + Arrays.toString(mCodecConfigInfo));
96             }
97             if (mLevel == 2) {
98                 log("mMetaDataLength: " + mMetaDataLength);
99                 if (mMetaDataLength != 0) {
100                     log("metaData: " + Arrays.toString(mMetaData));
101                 }
102                 if (mLevel == 1 || mLevel == 2) {
103                     log("mNumSubGroups: " + mNumSubGroups);
104                 }
105             }
106             log("**END: Base Information****");
107         }
108     }
109 
110     static BaseData parseBaseData(byte[] serviceData) {
111         if (serviceData == null) {
112             Log.e(TAG, "Invalid service data for BaseData construction");
113             throw new IllegalArgumentException("BaseData: serviceData is null");
114         }
115         BaseInformation levelOne = new BaseInformation();
116         List<BaseInformation> levelTwo = new ArrayList<>();
117         List<BaseInformation> levelThree = new ArrayList<>();
118         int numOfBISIndices = 0;
119         log("BASE input" + Arrays.toString(serviceData));
120 
121         // Parse Level 1 base
122         levelOne.mLevel = METADATA_LEVEL1;
123         int offset = 0;
124         System.arraycopy(serviceData, offset, levelOne.mPresentationDelay, 0, 3);
125         offset += METADATA_PRESENTATION_DELAY_LENGTH;
126         levelOne.mNumSubGroups = serviceData[offset++];
127         levelOne.print();
128         log("levelOne subgroups" + levelOne.mNumSubGroups);
129         for (int i = 0; i < (int) levelOne.mNumSubGroups; i++) {
130             if (offset >= serviceData.length) {
131                 Log.e(TAG, "Error: parsing Level 2");
132                 return null;
133             }
134 
135             Pair<BaseInformation, Integer> pair1 = parseLevelTwo(serviceData, i, offset);
136             if (pair1 == null) {
137                 Log.e(TAG, "Error: parsing Level 2");
138                 return null;
139             }
140             BaseInformation node2 = pair1.first;
141             numOfBISIndices += node2.mNumSubGroups;
142             levelTwo.add(node2);
143             node2.print();
144             offset = pair1.second;
145             for (int k = 0; k < node2.mNumSubGroups; k++) {
146                 if (offset >= serviceData.length) {
147                     Log.e(TAG, "Error: parsing Level 3");
148                     return null;
149                 }
150 
151                 Pair<BaseInformation, Integer> pair2 = parseLevelThree(serviceData, offset);
152                 if (pair2 == null) {
153                     Log.e(TAG, "Error: parsing Level 3");
154                     return null;
155                 }
156                 BaseInformation node3 = pair2.first;
157                 levelThree.add(node3);
158                 node3.print();
159                 offset = pair2.second;
160             }
161         }
162         consolidateBaseOfLevelTwo(levelTwo, levelThree);
163         return new BaseData(levelOne, levelTwo, levelThree, numOfBISIndices);
164     }
165 
166     private static Pair<BaseInformation, Integer> parseLevelTwo(
167             byte[] serviceData, int groupIndex, int offset) {
168         log("Parsing Level 2");
169         BaseInformation node = new BaseInformation();
170         node.mLevel = METADATA_LEVEL2;
171         node.mSubGroupId = groupIndex;
172         int bufferLengthLeft = (serviceData.length - offset);
173 
174         // Min. length expected is: mCodecID(5) + numBis(1) + codecSpecCfgLen(1) + metadataLen(1)
175         final int minNodeBufferLen = METADATA_CODEC_LENGTH + 3;
176         if (bufferLengthLeft < minNodeBufferLen) {
177             Log.e(TAG, "Error: Invalid Lvl2 buffer length.");
178             return null;
179         }
180 
181         node.mNumSubGroups = serviceData[offset++]; // NumBis
182         System.arraycopy(serviceData, offset, node.mCodecId, 0, METADATA_CODEC_LENGTH);
183         offset += METADATA_CODEC_LENGTH;
184 
185         // Declared codec specific data length
186         int declaredLength = serviceData[offset++] & 0xff;
187 
188         bufferLengthLeft = (serviceData.length - offset);
189         if (declaredLength < 0 || declaredLength > bufferLengthLeft) {
190             Log.e(TAG, "Error: Invalid codec config length or codec config truncated.");
191             return null;
192         }
193 
194         if (declaredLength != 0) {
195             node.mCodecConfigLength = declaredLength;
196             node.mCodecConfigInfo = new byte[node.mCodecConfigLength];
197             System.arraycopy(
198                     serviceData, offset, node.mCodecConfigInfo, 0, node.mCodecConfigLength);
199             offset += node.mCodecConfigLength;
200         }
201 
202         // Verify the buffer size left
203         bufferLengthLeft = (serviceData.length - offset);
204         if (bufferLengthLeft < 1) {
205             Log.e(TAG, "Error: Invalid Lvl2 buffer length.");
206             return null;
207         }
208 
209         // Declared metadata length
210         declaredLength = serviceData[offset++] & 0xff;
211         --bufferLengthLeft;
212         if (declaredLength < 0 || declaredLength > bufferLengthLeft) {
213             Log.e(TAG, "Error: Invalid metadata length or metadata truncated.");
214             return null;
215         }
216 
217         if (declaredLength != 0) {
218             node.mMetaDataLength = declaredLength;
219             node.mMetaData = new byte[node.mMetaDataLength];
220             System.arraycopy(serviceData, offset, node.mMetaData, 0, node.mMetaDataLength);
221             offset += node.mMetaDataLength;
222         }
223         return new Pair<BaseInformation, Integer>(node, offset);
224     }
225 
226     private static Pair<BaseInformation, Integer> parseLevelThree(byte[] serviceData, int offset) {
227         log("Parsing Level 3");
228         BaseInformation node = new BaseInformation();
229         node.mLevel = METADATA_LEVEL3;
230         int bufferLengthLeft = (serviceData.length - offset);
231 
232         // Min. length expected is: bisIdx (1) + codecSpecCfgLen (1)
233         final int minNodeBufferLen = 2;
234         if (bufferLengthLeft < minNodeBufferLen) {
235             Log.e(TAG, "Error: Invalid Lvl2 buffer length.");
236             return null;
237         }
238         node.mIndex = serviceData[offset++];
239 
240         // Verify the buffer size left
241         int declaredLength = serviceData[offset++] & 0xff;
242 
243         bufferLengthLeft = (serviceData.length - offset);
244         if (declaredLength < 0 || declaredLength > bufferLengthLeft) {
245             Log.e(TAG, "Error: Invalid metadata length or metadata truncated.");
246             return null;
247         }
248 
249         if (declaredLength != 0) {
250             node.mCodecConfigLength = declaredLength;
251             node.mCodecConfigInfo = new byte[node.mCodecConfigLength];
252             System.arraycopy(
253                     serviceData, offset, node.mCodecConfigInfo, 0, node.mCodecConfigLength);
254             offset += node.mCodecConfigLength;
255         }
256         return new Pair<BaseInformation, Integer>(node, offset);
257     }
258 
259     static void consolidateBaseOfLevelTwo(
260             List<BaseInformation> levelTwo, List<BaseInformation> levelThree) {
261         int startIdx = 0;
262         int children = 0;
263         for (int i = 0; i < levelTwo.size(); i++) {
264             startIdx = startIdx + children;
265             children = children + levelTwo.get(i).mNumSubGroups;
266             consolidateBaseOfLevelThree(
267                     levelTwo, levelThree, i, startIdx, levelTwo.get(i).mNumSubGroups);
268         }
269     }
270 
271     static void consolidateBaseOfLevelThree(
272             List<BaseInformation> levelTwo,
273             List<BaseInformation> levelThree,
274             int parentSubgroup,
275             int startIdx,
276             int numNodes) {
277         for (int i = startIdx; i < startIdx + numNodes || i < levelThree.size(); i++) {
278             levelThree.get(i).mSubGroupId = levelTwo.get(parentSubgroup).mSubGroupId;
279         }
280     }
281 
282     public byte getNumberOfSubGroupsOfBIG() {
283         byte ret = 0;
284         if (levelOne != null) {
285             ret = levelOne.mNumSubGroups;
286         }
287         return ret;
288     }
289 
290     public List<BaseInformation> getBISIndexInfos() {
291         return levelThree;
292     }
293 
294     byte[] getMetadata(int subGroup) {
295         if (levelTwo != null) {
296             return levelTwo.get(subGroup).mMetaData;
297         }
298         return null;
299     }
300 
301     String getMetadataString(byte[] metadataBytes) {
302         String ret = "";
303         switch (metadataBytes[1]) {
304             case METADATA_LANGUAGE_TYPE:
305                 char[] lang = new char[3];
306                 System.arraycopy(metadataBytes, 1, lang, 0, 3);
307                 Locale locale = new Locale(String.valueOf(lang));
308                 try {
309                     ret = locale.getISO3Language();
310                 } catch (MissingResourceException e) {
311                     ret = "UNKNOWN LANGUAGE";
312                 }
313                 break;
314             default:
315                 ret = "UNKNOWN METADATA TYPE";
316         }
317         log("getMetadataString: " + ret);
318         return ret;
319     }
320 
321     String getCodecParamString(byte[] csiBytes) {
322         String ret = "";
323         switch (csiBytes[1]) {
324             case CODEC_CONFIGURATION_CHANNEL_ALLOCATION_TYPE:
325                 byte[] location = new byte[4];
326                 System.arraycopy(csiBytes, 2, location, 0, 4);
327                 ByteBuffer wrapped = ByteBuffer.wrap(location);
328                 int audioLocation = wrapped.getInt();
329                 log("audioLocation: " + audioLocation);
330                 switch (audioLocation) {
331                     case CODEC_AUDIO_LOCATION_FRONT_LEFT:
332                         ret = "LEFT";
333                         break;
334                     case CODEC_AUDIO_LOCATION_FRONT_RIGHT:
335                         ret = "RIGHT";
336                         break;
337                     case CODEC_AUDIO_LOCATION_FRONT_LEFT | CODEC_AUDIO_LOCATION_FRONT_RIGHT:
338                         ret = "LR";
339                         break;
340                 }
341                 break;
342             case CODEC_CONFIGURATION_SAMPLE_RATE_TYPE:
343                 switch (csiBytes[2]) {
344                     case CODEC_AUDIO_SAMPLE_RATE_8K:
345                         ret = "8K";
346                         break;
347                     case CODEC_AUDIO_SAMPLE_RATE_16K:
348                         ret = "16K";
349                         break;
350                     case CODEC_AUDIO_SAMPLE_RATE_24K:
351                         ret = "24K";
352                         break;
353                     case CODEC_AUDIO_SAMPLE_RATE_32K:
354                         ret = "32K";
355                         break;
356                     case CODEC_AUDIO_SAMPLE_RATE_44P1K:
357                         ret = "44.1K";
358                         break;
359                     case CODEC_AUDIO_SAMPLE_RATE_48K:
360                         ret = "48K";
361                         break;
362                 }
363                 break;
364             case CODEC_CONFIGURATION_FRAME_DURATION_TYPE:
365                 switch (csiBytes[2]) {
366                     case CODEC_AUDIO_FRAME_DURATION_7P5MS:
367                         ret = "7.5ms";
368                         break;
369                     case CODEC_AUDIO_FRAME_DURATION_10MS:
370                         ret = "10ms";
371                         break;
372                 }
373                 break;
374             case CODEC_CONFIGURATION_OCTETS_PER_FRAME_TYPE:
375                 ret = "OPF_" + String.valueOf((int) csiBytes[2]);
376                 break;
377             default:
378                 ret = "UNKNOWN PARAMETER";
379         }
380         log("getCodecParamString: " + ret);
381         return ret;
382     }
383 
384     void print() {
385         levelOne.print();
386         log("----- Level TWO BASE ----");
387         levelTwo.stream().forEach(BaseInformation::print);
388         log("----- Level THREE BASE ----");
389         levelThree.stream().forEach(BaseInformation::print);
390     }
391 
392     static void log(String msg) {
393         Log.d(TAG, msg);
394     }
395 }
396