• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 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.HashMap;
26 import java.util.Iterator;
27 import java.util.LinkedHashSet;
28 import java.util.Locale;
29 import java.util.Map;
30 import java.util.MissingResourceException;
31 import java.util.Set;
32 
33 /**
34  * Helper class to parse the Broadcast Announcement BASE data
35  */
36 class BaseData {
37     private static final String TAG = "Bassclient-BaseData";
38     private static final byte UNKNOWN_CODEC = (byte) 0xFE;
39     private static final int METADATA_LEVEL1 = 1;
40     private static final int METADATA_LEVEL2 = 2;
41     private static final int METADATA_LEVEL3 = 3;
42     private static final int METADATA_PRESENTATIONDELAY_LENGTH = 3;
43     private static final int METADATA_CODEC_LENGTH = 5;
44     private static final int METADATA_UNKNOWN_CODEC_LENGTH = 1;
45     private static final int CODEC_CAPABILITIES_SAMPLE_RATE_TYPE = 1;
46     private static final int CODEC_CAPABILITIES_FRAME_DURATION_TYPE = 2;
47     private static final int CODEC_CAPABILITIES_CHANNEL_COUNT_TYPE = 3;
48     private static final int CODEC_CAPABILITIES_OCTETS_PER_FRAME_TYPE = 4;
49     private static final int CODEC_CAPABILITIES_MAX_FRAMES_PER_SDU_TYPE = 5;
50     private static final int CODEC_CONFIGURATION_SAMPLE_RATE_TYPE = 0x01;
51     private static final int CODEC_CONFIGURATION_FRAME_DURATION_TYPE = 0x02;
52     private static final int CODEC_CONFIGURATION_CHANNEL_ALLOCATION_TYPE = 0x03;
53     private static final int CODEC_CONFIGURATION_OCTETS_PER_FRAME_TYPE = 0x04;
54     private static final int CODEC_CONFIGURATION_BLOCKS_PER_SDU_TYPE = 0x05;
55     private static final int METADATA_PREFERRED_CONTEXTS_TYPE = 0x01;
56     private static final int METADATA_STREAMING_CONTEXTS_TYPE = 0x02;
57     private static final int METADATA_PROGRAM_INFO_TYPE = 0x03;
58     private static final int METADATA_LANGUAGE_TYPE = 0x04;
59     private static final int METADATA_CCID_LIST_TYPE = 0x05;
60     private static final int METADATA_PARENTAL_RATING_TYPE = 0x06;
61     private static final int METADATA_PROGRAM_INFO_URI_TYPE = 0x07;
62     private static final int METADATA_EXTENDED_TYPE = 0xFE;
63     private static final int METADATA_VENDOR_TYPE = 0xFF;
64     private static final int CODEC_AUDIO_LOCATION_FRONT_LEFT = 0x01000000;
65     private static final int CODEC_AUDIO_LOCATION_FRONT_RIGHT = 0x02000000;
66     private static final int CODEC_AUDIO_SAMPLE_RATE_8K = 0x01;
67     private static final int CODEC_AUDIO_SAMPLE_RATE_16K = 0x03;
68     private static final int CODEC_AUDIO_SAMPLE_RATE_24K = 0x05;
69     private static final int CODEC_AUDIO_SAMPLE_RATE_32K = 0x06;
70     private static final int CODEC_AUDIO_SAMPLE_RATE_44P1K = 0x07;
71     private static final int CODEC_AUDIO_SAMPLE_RATE_48K = 0x08;
72     private static final int CODEC_AUDIO_FRAME_DURATION_7P5MS = 0x00;
73     private static final int CODEC_AUDIO_FRAME_DURATION_10MS = 0x01;
74 
75     private final BaseInformation mLevelOne;
76     private final ArrayList<BaseInformation> mLevelTwo;
77     private final ArrayList<BaseInformation> mLevelThree;
78 
79     private int mNumBISIndices = 0;
80 
81     public static class BaseInformation {
82         public byte[] presentationDelay = new byte[3];
83         public byte[] codecId = new byte[5];
84         public byte codecConfigLength;
85         public byte[] codecConfigInfo;
86         public byte metaDataLength;
87         public byte[] metaData;
88         public byte numSubGroups;
89         public byte[] bisIndices;
90         public byte index;
91         public int subGroupId;
92         public int level;
93         public LinkedHashSet<String> keyCodecCfgDiff;
94         public LinkedHashSet<String> keyMetadataDiff;
95         public String diffText;
96         public String description;
97         public byte[] consolidatedCodecId;
98         public Set<String> consolidatedMetadata;
99         public Set<String> consolidatedCodecInfo;
100         public HashMap<Integer, String> consolidatedUniqueCodecInfo;
101         public HashMap<Integer, String> consolidatedUniqueMetadata;
102 
BaseInformation()103         BaseInformation() {
104             presentationDelay = new byte[3];
105             codecId = new byte[5];
106             codecConfigLength = 0;
107             codecConfigInfo = new byte[0];
108             metaDataLength = 0;
109             metaData = new byte[0];
110             numSubGroups = 0;
111             bisIndices = null;
112             index = (byte) 0xFF;
113             level = 0;
114             keyCodecCfgDiff = new LinkedHashSet<String>();
115             keyMetadataDiff = new LinkedHashSet<String>();
116             consolidatedMetadata = new LinkedHashSet<String>();
117             consolidatedCodecInfo = new LinkedHashSet<String>();
118             consolidatedCodecId = new byte[5];
119             consolidatedUniqueMetadata = new HashMap<Integer, String>();
120             consolidatedUniqueCodecInfo = new HashMap<Integer, String>();
121             diffText = new String("");
122             description = new String("");
123             log("BaseInformation is Initialized");
124         }
125 
isCodecIdUnknown()126         boolean isCodecIdUnknown() {
127             return (codecId != null && codecId[4] == (byte) UNKNOWN_CODEC);
128         }
129 
print()130         void print() {
131             log("**BEGIN: Base Information**");
132             log("**Level: " + level + "***");
133             if (level == 1) {
134                 log("presentationDelay: " + Arrays.toString(presentationDelay));
135             }
136             if (level == 2) {
137                 log("codecId: " + Arrays.toString(codecId));
138             }
139             if (level == 2 || level == 3) {
140                 log("codecConfigLength: " + codecConfigLength);
141                 log("subGroupId: " + subGroupId);
142             }
143             if (codecConfigLength != (byte) 0) {
144                 log("codecConfigInfo: " + Arrays.toString(codecConfigInfo));
145             }
146             if (level == 2) {
147                 log("metaDataLength: " + metaDataLength);
148                 if (metaDataLength != (byte) 0) {
149                     log("metaData: " + Arrays.toString(metaData));
150                 }
151                 if (level == 1 || level == 2) {
152                     log("numSubGroups: " + numSubGroups);
153                 }
154             }
155             if (level == 2) {
156                 log("Level2: Key Metadata differentiators");
157                 if (keyMetadataDiff != null) {
158                     Iterator<String> itr = keyMetadataDiff.iterator();
159                     for (int k = 0; itr.hasNext(); k++) {
160                         log("keyMetadataDiff:[" + k + "]:"
161                                 + Arrays.toString(itr.next().getBytes()));
162                     }
163                 }
164                 log("END: Level2: Key Metadata differentiators");
165                 log("Level2: Key CodecConfig differentiators");
166                 if (keyCodecCfgDiff != null) {
167                     Iterator<String> itr = keyCodecCfgDiff.iterator();
168                     for (int k = 0; itr.hasNext(); k++) {
169                         log("LEVEL2: keyCodecCfgDiff:[" + k + "]:"
170                                 + Arrays.toString(itr.next().getBytes()));
171                     }
172                 }
173                 log("END: Level2: Key CodecConfig differentiators");
174                 log("LEVEL2: diffText: " + diffText);
175             }
176             if (level == 3) {
177                 log("Level3: Key CodecConfig differentiators");
178                 if (keyCodecCfgDiff != null) {
179                     Iterator<String> itr = keyCodecCfgDiff.iterator();
180                     for (int k = 0; itr.hasNext(); k++) {
181                         log("LEVEL3: keyCodecCfgDiff:[" + k + "]:"
182                                 + Arrays.toString(itr.next().getBytes()));
183                     }
184                 }
185                 log("END: Level3: Key CodecConfig differentiators");
186                 log("index: " + index);
187                 log("LEVEL3: diffText: " + diffText);
188             }
189             log("**END: Base Information****");
190         }
191     }
192 
BaseData(BaseInformation levelOne, ArrayList<BaseInformation> levelTwo, ArrayList<BaseInformation> levelThree, int numOfBISIndices)193     BaseData(BaseInformation levelOne, ArrayList<BaseInformation> levelTwo,
194              ArrayList<BaseInformation> levelThree, int numOfBISIndices) {
195         mLevelOne = levelOne;
196         mLevelTwo = levelTwo;
197         mLevelThree = levelThree;
198         mNumBISIndices = numOfBISIndices;
199     }
200 
parseBaseData(byte[] serviceData)201     static BaseData parseBaseData(byte[] serviceData) {
202         if (serviceData == null) {
203             Log.e(TAG, "Invalid service data for BaseData construction");
204             throw new IllegalArgumentException("Basedata: serviceData is null");
205         }
206         BaseInformation levelOne = new BaseInformation();
207         ArrayList<BaseInformation> levelTwo = new ArrayList<BaseInformation>();
208         ArrayList<BaseInformation> levelThree = new ArrayList<BaseInformation>();
209         int numOfBISIndices = 0;
210         log("BASE input" + Arrays.toString(serviceData));
211 
212         // Parse Level 1 base
213         levelOne.level = METADATA_LEVEL1;
214         int offset = 0;
215         System.arraycopy(serviceData, offset, levelOne.presentationDelay, 0, 3);
216         offset += METADATA_PRESENTATIONDELAY_LENGTH;
217         levelOne.numSubGroups = serviceData[offset++];
218         levelOne.print();
219         log("levelOne subgroups" + levelOne.numSubGroups);
220         for (int i = 0; i < (int) levelOne.numSubGroups; i++) {
221             Pair<BaseInformation, Integer> pair1 =
222                     parseLevelTwo(serviceData, i, offset);
223             BaseInformation node2 = pair1.first;
224             if (node2 == null) {
225                 Log.e(TAG, "Error: parsing Level 2");
226                 return null;
227             }
228             numOfBISIndices += node2.numSubGroups;
229             levelTwo.add(node2);
230             node2.print();
231             offset = pair1.second;
232             for (int k = 0; k < node2.numSubGroups; k++) {
233                 Pair<BaseInformation, Integer> pair2 =
234                         parseLevelThree(serviceData, offset);
235                 BaseInformation node3 = pair2.first;
236                 offset = pair2.second;
237                 if (node3 == null) {
238                     Log.e(TAG, "Error: parsing Level 3");
239                     return null;
240                 }
241                 levelThree.add(node3);
242                 node3.print();
243             }
244         }
245         consolidateBaseofLevelTwo(levelTwo, levelThree);
246         return new BaseData(levelOne, levelTwo, levelThree, numOfBISIndices);
247     }
248 
249     private static Pair<BaseInformation, Integer>
parseLevelTwo(byte[] serviceData, int groupIndex, int offset)250             parseLevelTwo(byte[] serviceData, int groupIndex, int offset) {
251         log("Parsing Level 2");
252         BaseInformation node = new BaseInformation();
253         node.level = METADATA_LEVEL2;
254         node.subGroupId = groupIndex;
255         node.numSubGroups = serviceData[offset++];
256         if (serviceData[offset] == (byte) UNKNOWN_CODEC) {
257             // Place It in the last byte of codecID
258             System.arraycopy(serviceData, offset, node.codecId,
259                     METADATA_CODEC_LENGTH - 1, METADATA_UNKNOWN_CODEC_LENGTH);
260             offset += METADATA_UNKNOWN_CODEC_LENGTH;
261             log("codecId is FE");
262         } else {
263             System.arraycopy(serviceData, offset, node.codecId,
264                     0, METADATA_CODEC_LENGTH);
265             offset += METADATA_CODEC_LENGTH;
266         }
267         node.codecConfigLength = serviceData[offset++];
268         if (node.codecConfigLength != 0) {
269             node.codecConfigInfo = new byte[(int) node.codecConfigLength];
270             System.arraycopy(serviceData, offset, node.codecConfigInfo,
271                     0, (int) node.codecConfigLength);
272             offset += node.codecConfigLength;
273         }
274         node.metaDataLength = serviceData[offset++];
275         if (node.metaDataLength != 0) {
276             node.metaData = new byte[(int) node.metaDataLength];
277             System.arraycopy(serviceData, offset,
278                     node.metaData, 0, (int) node.metaDataLength);
279             offset += node.metaDataLength;
280         }
281         return new Pair<BaseInformation, Integer>(node, offset);
282     }
283 
284     private static Pair<BaseInformation, Integer>
parseLevelThree(byte[] serviceData, int offset)285             parseLevelThree(byte[] serviceData, int offset) {
286         log("Parsing Level 3");
287         BaseInformation node = new BaseInformation();
288         node.level = METADATA_LEVEL3;
289         node.index = serviceData[offset++];
290         node.codecConfigLength = serviceData[offset++];
291         if (node.codecConfigLength != 0) {
292             node.codecConfigInfo = new byte[(int) node.codecConfigLength];
293             System.arraycopy(serviceData, offset,
294                     node.codecConfigInfo, 0, (int) node.codecConfigLength);
295             offset += node.codecConfigLength;
296         }
297         return new Pair<BaseInformation, Integer>(node, offset);
298     }
299 
consolidateBaseofLevelTwo(ArrayList<BaseInformation> levelTwo, ArrayList<BaseInformation> levelThree)300     static void consolidateBaseofLevelTwo(ArrayList<BaseInformation> levelTwo,
301             ArrayList<BaseInformation> levelThree) {
302         int startIdx = 0;
303         int children = 0;
304         for (int i = 0; i < levelTwo.size(); i++) {
305             startIdx = startIdx + children;
306             children = children + levelTwo.get(i).numSubGroups;
307             consolidateBaseofLevelThree(levelTwo, levelThree,
308                     i, startIdx, levelTwo.get(i).numSubGroups);
309         }
310         // Eliminate Duplicates at Level 3
311         for (int i = 0; i < levelThree.size(); i++) {
312             Map<Integer, String> uniqueMds = new HashMap<Integer, String>();
313             Map<Integer, String> uniqueCcis = new HashMap<Integer, String>();
314             Set<String> Csfs = levelThree.get(i).consolidatedCodecInfo;
315             if (Csfs.size() > 0) {
316                 Iterator<String> itr = Csfs.iterator();
317                 for (int j = 0; itr.hasNext(); j++) {
318                     byte[] ltvEntries = itr.next().getBytes();
319                     int k = 0;
320                     byte length = ltvEntries[k++];
321                     byte[] ltv = new byte[length + 1];
322                     ltv[0] = length;
323                     System.arraycopy(ltvEntries, k, ltv, 1, length);
324                     int type = (int) ltv[1];
325                     String s = uniqueCcis.get(type);
326                     String ltvS = new String(ltv);
327                     if (s == null) {
328                         uniqueCcis.put(type, ltvS);
329                     } else {
330                         // if same type exists, replace
331                         uniqueCcis.replace(type, ltvS);
332                     }
333                 }
334             }
335             Set<String> Mds = levelThree.get(i).consolidatedMetadata;
336             if (Mds.size() > 0) {
337                 Iterator<String> itr = Mds.iterator();
338                 for (int j = 0; itr.hasNext(); j++) {
339                     byte[] ltvEntries = itr.next().getBytes();
340                     int k = 0;
341                     byte length = ltvEntries[k++];
342                     byte[] ltv = new byte[length + 1];
343                     ltv[0] = length;
344                     System.arraycopy(ltvEntries, k, ltv, 1, length);
345                     int type = (int) ltv[1];
346                     String s = uniqueCcis.get(type);
347                     String ltvS = new String(ltv);
348                     if (s == null) {
349                         uniqueMds.put(type, ltvS);
350                     } else {
351                         uniqueMds.replace(type, ltvS);
352                     }
353                 }
354             }
355             levelThree.get(i).consolidatedUniqueMetadata = new HashMap<Integer, String>(uniqueMds);
356             levelThree.get(i).consolidatedUniqueCodecInfo =
357                     new HashMap<Integer, String>(uniqueCcis);
358         }
359     }
360 
consolidateBaseofLevelThree(ArrayList<BaseInformation> levelTwo, ArrayList<BaseInformation> levelThree, int parentSubgroup, int startIdx, int numNodes)361     static void consolidateBaseofLevelThree(ArrayList<BaseInformation> levelTwo,
362             ArrayList<BaseInformation> levelThree, int parentSubgroup, int startIdx, int numNodes) {
363         for (int i = startIdx; i < startIdx + numNodes || i < levelThree.size(); i++) {
364             levelThree.get(i).subGroupId = levelTwo.get(parentSubgroup).subGroupId;
365             log("Copy Codec Id from Level2 Parent" + parentSubgroup);
366             System.arraycopy(
367                     levelTwo.get(parentSubgroup).consolidatedCodecId,
368                     0, levelThree.get(i).consolidatedCodecId, 0, 5);
369             // Metadata clone from Parent
370             levelThree.get(i).consolidatedMetadata =
371                     new LinkedHashSet<String>(levelTwo.get(parentSubgroup).consolidatedMetadata);
372             // CCI clone from Parent
373             levelThree.get(i).consolidatedCodecInfo =
374                     new LinkedHashSet<String>(levelTwo.get(parentSubgroup).consolidatedCodecInfo);
375             // Append Level 2 Codec Config
376             if (levelThree.get(i).codecConfigLength != 0) {
377                 log("append level 3 cci to level 3 cons:" + i);
378                 String s = new String(levelThree.get(i).codecConfigInfo);
379                 levelThree.get(i).consolidatedCodecInfo.add(s);
380             }
381         }
382     }
383 
getNumberOfIndices()384     public int getNumberOfIndices() {
385         return mNumBISIndices;
386     }
387 
getLevelOne()388     public BaseInformation  getLevelOne() {
389         return mLevelOne;
390     }
391 
getLevelTwo()392     public ArrayList<BaseInformation> getLevelTwo() {
393         return mLevelTwo;
394     }
395 
getLevelThree()396     public ArrayList<BaseInformation> getLevelThree() {
397         return mLevelThree;
398     }
399 
getNumberOfSubgroupsofBIG()400     public byte getNumberOfSubgroupsofBIG() {
401         byte ret = 0;
402         if (mLevelOne != null) {
403             ret = mLevelOne.numSubGroups;
404         }
405         return ret;
406     }
407 
getBISIndexInfos()408     public ArrayList<BaseInformation> getBISIndexInfos() {
409         return mLevelThree;
410     }
411 
getMetadata(int subGroup)412     byte[] getMetadata(int subGroup) {
413         if (mLevelTwo != null) {
414             return mLevelTwo.get(subGroup).metaData;
415         }
416         return null;
417     }
418 
getMetadataString(byte[] metadataBytes)419     String getMetadataString(byte[] metadataBytes) {
420         String ret = "";
421         switch (metadataBytes[1]) {
422             case METADATA_LANGUAGE_TYPE:
423                 char[] lang = new char[3];
424                 System.arraycopy(metadataBytes, 1, lang, 0, 3);
425                 Locale locale = new Locale(String.valueOf(lang));
426                 try {
427                     ret = locale.getISO3Language();
428                 } catch (MissingResourceException e) {
429                     ret = "UNKNOWN LANGUAGE";
430                 }
431                 break;
432             default:
433                 ret = "UNKNOWN METADATA TYPE";
434         }
435         log("getMetadataString: " + ret);
436         return ret;
437     }
438 
getCodecParamString(byte[] csiBytes)439     String getCodecParamString(byte[] csiBytes) {
440         String ret = "";
441         switch (csiBytes[1]) {
442             case CODEC_CONFIGURATION_CHANNEL_ALLOCATION_TYPE:
443                 byte[] location = new byte[4];
444                 System.arraycopy(csiBytes, 2, location, 0, 4);
445                 ByteBuffer wrapped = ByteBuffer.wrap(location);
446                 int audioLocation = wrapped.getInt();
447                 log("audioLocation: " + audioLocation);
448                 switch (audioLocation) {
449                     case CODEC_AUDIO_LOCATION_FRONT_LEFT:
450                         ret = "LEFT";
451                         break;
452                     case CODEC_AUDIO_LOCATION_FRONT_RIGHT:
453                         ret = "RIGHT";
454                         break;
455                     case CODEC_AUDIO_LOCATION_FRONT_LEFT
456                             | CODEC_AUDIO_LOCATION_FRONT_RIGHT:
457                         ret = "LR";
458                         break;
459                 }
460                 break;
461             case CODEC_CONFIGURATION_SAMPLE_RATE_TYPE:
462                 switch (csiBytes[2]) {
463                     case CODEC_AUDIO_SAMPLE_RATE_8K:
464                         ret = "8K";
465                         break;
466                     case CODEC_AUDIO_SAMPLE_RATE_16K:
467                         ret = "16K";
468                         break;
469                     case CODEC_AUDIO_SAMPLE_RATE_24K:
470                         ret = "24K";
471                         break;
472                     case CODEC_AUDIO_SAMPLE_RATE_32K:
473                         ret = "32K";
474                         break;
475                     case CODEC_AUDIO_SAMPLE_RATE_44P1K:
476                         ret = "44.1K";
477                         break;
478                     case CODEC_AUDIO_SAMPLE_RATE_48K:
479                         ret = "48K";
480                         break;
481                 }
482                 break;
483             case CODEC_CONFIGURATION_FRAME_DURATION_TYPE:
484                 switch (csiBytes[2]) {
485                     case CODEC_AUDIO_FRAME_DURATION_7P5MS:
486                         ret = "7.5ms";
487                         break;
488                     case CODEC_AUDIO_FRAME_DURATION_10MS:
489                         ret = "10ms";
490                         break;
491                 }
492                 break;
493             case CODEC_CONFIGURATION_OCTETS_PER_FRAME_TYPE:
494                 ret = "OPF_" + String.valueOf((int) csiBytes[2]);
495                 break;
496             default:
497                 ret = "UNKNOWN PARAMETER";
498         }
499         log("getCodecParamString: " + ret);
500         return ret;
501     }
502 
print()503     void print() {
504         mLevelOne.print();
505         log("----- Level TWO BASE ----");
506         for (int i = 0; i < mLevelTwo.size(); i++) {
507             mLevelTwo.get(i).print();
508         }
509         log("----- Level THREE BASE ----");
510         for (int i = 0; i < mLevelThree.size(); i++) {
511             mLevelThree.get(i).print();
512         }
513     }
514 
log(String msg)515     static void log(String msg) {
516         if (BassConstants.BASS_DBG) {
517             Log.d(TAG, msg);
518         }
519     }
520 }
521