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