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