blob: ca2eed2027bd588bdc5a9728b0a74d731fe3031f [file] [log] [blame]
weichinweng9bbaa092018-12-20 15:03:37 +08001#!/usr/bin/env python
2# Copyright 2018 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may not
5# use this file except in compliance with the License. You may obtain a copy of
6# 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, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations under
14# the License.
15#
16#
17#
18#
19# This script extracts Hearing Aid audio data from btsnoop.
20# Generates a valid audio file which can be played using player like smplayer.
21#
22# Audio File Name Format:
weichinweng49c14442019-04-19 17:27:32 +080023# [PEER_ADDRESS]-[START_TIMESTAMP]-[AUDIO_TYPE]-[SAMPLE_RATE].[CODEC]
24#
25# Debug Infomation File Name Format:
26# debug_ver_[DEBUG_VERSION]-[PEER_ADDRESS]-[START_TIMESTAMP]-[AUDIO_TYPE]-[SAMPLE_RATE].txt
weichinweng9bbaa092018-12-20 15:03:37 +080027#
28# Player:
29# smplayer
30#
31# NOTE:
32# Please make sure you HCI Snoop data file includes the following frames:
33# HearingAid "LE Enhanced Connection Complete", GATT write for Audio Control
34# Point with "Start cmd", and the data frames.
35
36import argparse
37import os
38import struct
39import sys
40import time
41
42IS_SENT = "IS_SENT"
43PEER_ADDRESS = "PEER_ADDRESS"
44CONNECTION_HANDLE = "CONNECTION_HANDLE"
45AUDIO_CONTROL_ATTR_HANDLE = "AUDIO_CONTROL_ATTR_HANDLE"
46START = "START"
weichinweng49c14442019-04-19 17:27:32 +080047TIMESTAMP_STR_FORMAT = "TIMESTAMP_STR_FORMAT"
48TIMESTAMP_TIME_FORMAT = "TIMESTAMP_TIME_FORMAT"
weichinweng9bbaa092018-12-20 15:03:37 +080049CODEC = "CODEC"
50SAMPLE_RATE = "SAMPLE_RATE"
51AUDIO_TYPE = "AUDIO_TYPE"
weichinweng49c14442019-04-19 17:27:32 +080052DEBUG_VERSION = "DEBUG_VERSION"
53DEBUG_DATA = "DEBUG_DATA"
weichinweng9bbaa092018-12-20 15:03:37 +080054AUDIO_DATA_B = "AUDIO_DATA_B"
55
weichinweng49c14442019-04-19 17:27:32 +080056# Debug packet header struct
57header_list_str = ["Event Processed",
58 "Number Packet Nacked By Slave",
59 "Number Packet Nacked By Master"]
60# Debug frame information structs
61data_list_str = ["Event Number",
62 "Overrun",
63 "Underrun",
64 "Skips",
65 "Rendered Audio Frame",
66 "First PDU Option",
67 "Second PDU Option",
68 "Third PDU Option"]
69
weichinweng9bbaa092018-12-20 15:03:37 +080070AUDIO_CONTROL_POINT_UUID = "f0d4de7e4a88476c9d9f1937b0996cc0"
71SEC_CONVERT = 1000000
72folder = None
weichinweng49c14442019-04-19 17:27:32 +080073full_debug = False
74simple_debug = False
weichinweng9bbaa092018-12-20 15:03:37 +080075
76force_audio_control_attr_handle = None
77default_audio_control_attr_handle = 0x0079
78
79audio_data = {}
80
81#=======================================================================
82# Parse ACL Data Function
83#=======================================================================
84
85#-----------------------------------------------------------------------
86# Parse Hearing Aid Packet
87#-----------------------------------------------------------------------
88
weichinweng49c14442019-04-19 17:27:32 +080089def parse_acl_ha_debug_buffer(data, result):
90 """This function extracts HA debug buffer"""
91 if len(data) < 5:
92 return
93
94 version, data = unpack_data(data, 1)
95 update_audio_data(CONNECTION_HANDLE, result[CONNECTION_HANDLE], DEBUG_VERSION, str(version))
96
97 debug_str = result[TIMESTAMP_TIME_FORMAT];
98 for p in range(3):
99 byte_data, data = unpack_data(data, 1)
100 debug_str = debug_str + ", " + header_list_str[p] + "=" + str(byte_data).rjust(3)
101
102 if full_debug:
103 debug_str = debug_str + "\n" + "|".join(data_list_str) + "\n"
104 while True:
105 if len(data) < 7:
106 break
107 base = 0
108 data_list_content = []
109 for counter in range(6):
110 p = base + counter
111 byte_data, data = unpack_data(data, 1)
112 if p == 1:
113 data_list_content.append(str(byte_data & 0x03).rjust(len(data_list_str[p])))
114 data_list_content.append(str((byte_data >> 2) & 0x03).rjust(len(data_list_str[p + 1])))
115 data_list_content.append(str((byte_data >> 4) & 0x0f).rjust(len(data_list_str[p + 2])))
116 base = 2
117 else:
118 data_list_content.append(str(byte_data).rjust(len(data_list_str[p])))
119 debug_str = debug_str + "|".join(data_list_content) + "\n"
120
121 update_audio_data(CONNECTION_HANDLE, result[CONNECTION_HANDLE], DEBUG_DATA, debug_str)
weichinweng9bbaa092018-12-20 15:03:37 +0800122
123def parse_acl_ha_audio_data(data, result):
124 """This function extracts HA audio data."""
125 if len(data) < 2:
126 return
127 # Remove audio packet number
128 audio_data_b = data[1:]
129 update_audio_data(CONNECTION_HANDLE, result[CONNECTION_HANDLE],
130 AUDIO_DATA_B, audio_data_b)
131
132
133def parse_acl_ha_audio_type(data, result):
134 """This function parses HA audio control cmd audio type."""
135 audio_type, data = unpack_data(data, 1)
136 if audio_type is None:
137 return
138 elif audio_type == 0x01:
139 audio_type = "Ringtone"
140 elif audio_type == 0x02:
141 audio_type = "Phonecall"
142 elif audio_type == 0x03:
143 audio_type = "Media"
144 else:
145 audio_type = "Unknown"
146 update_audio_data(CONNECTION_HANDLE, result[CONNECTION_HANDLE],
147 AUDIO_TYPE, audio_type)
148
149
150def parse_acl_ha_codec(data, result):
151 """This function parses HA audio control cmd codec and sample rate."""
152 codec, data = unpack_data(data, 1)
153 if codec == 0x01:
154 codec = "G722"
155 sample_rate = "16KHZ"
156 elif codec == 0x02:
157 codec = "G722"
158 sample_rate = "24KHZ"
159 else:
160 codec = "Unknown"
161 sample_rate = "Unknown"
162 update_audio_data(CONNECTION_HANDLE, result[CONNECTION_HANDLE],
163 CODEC, codec)
164 update_audio_data(CONNECTION_HANDLE, result[CONNECTION_HANDLE],
165 SAMPLE_RATE, sample_rate)
166 parse_acl_ha_audio_type(data, result)
167
168
169def parse_acl_ha_audio_control_cmd(data, result):
170 """This function parses HA audio control cmd is start/stop."""
171 control_cmd, data = unpack_data(data, 1)
172 if control_cmd == 0x01:
173 update_audio_data(CONNECTION_HANDLE, result[CONNECTION_HANDLE],
174 START, True)
175 update_audio_data(CONNECTION_HANDLE, result[CONNECTION_HANDLE],
weichinweng49c14442019-04-19 17:27:32 +0800176 TIMESTAMP_STR_FORMAT, result[TIMESTAMP_STR_FORMAT])
weichinweng9bbaa092018-12-20 15:03:37 +0800177 parse_acl_ha_codec(data, result)
178 elif control_cmd == 0x02:
179 update_audio_data(CONNECTION_HANDLE, result[CONNECTION_HANDLE],
180 START, False)
181
182
183#-----------------------------------------------------------------------
184# Parse ACL Packet
185#-----------------------------------------------------------------------
186
187def parse_acl_att_long_uuid(data, result):
188 """This function parses ATT long UUID to get attr_handle."""
189 # len (1 byte) + start_attr_handle (2 bytes) + properties (1 byte) +
190 # attr_handle (2 bytes) + long_uuid (16 bytes) = 22 bytes
191 if len(data) < 22:
192 return
193 # skip unpack len, start_attr_handle, properties.
194 data = data[4:]
195 attr_handle, data = unpack_data(data, 2)
196 long_uuid_list = []
197 for p in range(0, 16):
198 long_uuid_list.append("{0:02x}".format(struct.unpack(">B", data[p])[0]))
199 long_uuid_list.reverse()
200 long_uuid = "".join(long_uuid_list)
201 # Check long_uuid is AUDIO_CONTROL_POINT uuid to get the attr_handle.
202 if long_uuid == AUDIO_CONTROL_POINT_UUID:
203 update_audio_data(CONNECTION_HANDLE, result[CONNECTION_HANDLE],
204 AUDIO_CONTROL_ATTR_HANDLE, attr_handle)
205
206
207def parse_acl_opcode(data, result):
208 """This function parses acl data opcode."""
209 # opcode (1 byte) = 1 bytes
210 if len(data) < 1:
211 return
212 opcode, data = unpack_data(data, 1)
213 # Check opcode is 0x12 (write request) and attr_handle is
214 # audio_control_attr_handle for check it is HA audio control cmd.
215 if result[IS_SENT] and opcode == 0x12:
216 if len(data) < 2:
217 return
218 attr_handle, data = unpack_data(data, 2)
219 if attr_handle == \
220 get_audio_control_attr_handle(result[CONNECTION_HANDLE]):
221 parse_acl_ha_audio_control_cmd(data, result)
222 # Check opcode is 0x09 (read response) to parse ATT long UUID.
223 elif not result[IS_SENT] and opcode == 0x09:
224 parse_acl_att_long_uuid(data, result)
225
226
227def parse_acl_handle(data, result):
228 """This function parses acl data handle."""
229 # connection_handle (2 bytes) + total_len (2 bytes) + pdu (2 bytes)
230 # + channel_id (2 bytes) = 8 bytes
231 if len(data) < 8:
232 return
233 connection_handle, data = unpack_data(data, 2)
234 connection_handle = connection_handle & 0x0FFF
235 # skip unpack total_len
236 data = data[2:]
237 pdu, data = unpack_data(data, 2)
238 channel_id, data = unpack_data(data, 2)
239
240 # Check ATT packet or "Coc Data Packet" to get ATT information and audio
241 # data.
242 if connection_handle <= 0x0EFF:
243 if channel_id <= 0x003F:
244 result[CONNECTION_HANDLE] = connection_handle
245 parse_acl_opcode(data, result)
weichinweng49c14442019-04-19 17:27:32 +0800246 elif channel_id >= 0x0040 and channel_id <= 0x007F:
weichinweng9bbaa092018-12-20 15:03:37 +0800247 result[CONNECTION_HANDLE] = connection_handle
248 sdu, data = unpack_data(data, 2)
249 if pdu - 2 == sdu:
weichinweng49c14442019-04-19 17:27:32 +0800250 if result[IS_SENT]:
251 parse_acl_ha_audio_data(data, result)
252 else:
253 if simple_debug:
254 parse_acl_ha_debug_buffer(data, result)
weichinweng9bbaa092018-12-20 15:03:37 +0800255
256
257#=======================================================================
258# Parse HCI EVT Function
259#=======================================================================
260
261
262def parse_hci_evt_peer_address(data, result):
263 """This function parses peer address from hci event."""
264 peer_address_list = []
265 address_empty_list = ["00", "00", "00", "00", "00", "00"]
266 for n in range(0, 3):
267 if len(data) < 6:
268 return
269 for p in range(0, 6):
270 peer_address_list.append("{0:02x}".format(struct.unpack(">B",
271 data[p])[0]))
272 # Check the address is empty or not.
273 if peer_address_list == address_empty_list:
274 del peer_address_list[:]
275 data = data[6:]
276 else:
277 break
278 peer_address_list.reverse()
279 peer_address = "_".join(peer_address_list)
280 update_audio_data("", "", PEER_ADDRESS, peer_address)
281 update_audio_data(PEER_ADDRESS, peer_address, CONNECTION_HANDLE,
282 result[CONNECTION_HANDLE])
283
284
285def parse_hci_evt_code(data, result):
286 """This function parses hci event content."""
287 # hci_evt (1 byte) + param_total_len (1 byte) + sub_event (1 byte)
288 # + status (1 byte) + connection_handle (2 bytes) + role (1 byte)
289 # + address_type (1 byte) = 8 bytes
290 if len(data) < 8:
291 return
292 hci_evt, data = unpack_data(data, 1)
293 # skip unpack param_total_len.
294 data = data[1:]
295 sub_event, data = unpack_data(data, 1)
296 status, data = unpack_data(data, 1)
297 connection_handle, data = unpack_data(data, 2)
298 connection_handle = connection_handle & 0x0FFF
299 # skip unpack role, address_type.
300 data = data[2:]
301 # We will directly check it is LE Enhanced Connection Complete or not
302 # for get Connection Handle and Address.
303 if not result[IS_SENT] and hci_evt == 0x3E and sub_event == 0x0A \
304 and status == 0x00 and connection_handle <= 0x0EFF:
305 result[CONNECTION_HANDLE] = connection_handle
306 parse_hci_evt_peer_address(data, result)
307
308
309#=======================================================================
310# Common Parse Function
311#=======================================================================
312
313
314def parse_packet_data(data, result):
315 """This function parses packet type."""
316 packet_type, data = unpack_data(data, 1)
317 if packet_type == 0x02:
318 # Try to check HearingAid audio control packet and data packet.
319 parse_acl_handle(data, result)
320 elif packet_type == 0x04:
321 # Try to check HearingAid connection successful packet.
322 parse_hci_evt_code(data, result)
323
324
325def parse_packet(btsnoop_file):
326 """This function parses packet len, timestamp."""
327 packet_result = {}
328
329 # ori_len (4 bytes) + include_len (4 bytes) + packet_flag (4 bytes)
330 # + drop (4 bytes) + timestamp (8 bytes) = 24 bytes
331 packet_header = btsnoop_file.read(24)
332 if len(packet_header) != 24:
333 return False
334
335 ori_len, include_len, packet_flag, drop, timestamp = \
336 struct.unpack(">IIIIq", packet_header)
337
338 if ori_len == include_len:
339 packet_data = btsnoop_file.read(ori_len)
340 if len(packet_data) != ori_len:
341 return False
342 if packet_flag != 2 and drop == 0:
343 packet_result[IS_SENT] = (packet_flag == 0)
weichinweng49c14442019-04-19 17:27:32 +0800344 packet_result[TIMESTAMP_STR_FORMAT], packet_result[TIMESTAMP_TIME_FORMAT] = convert_time_str(timestamp)
weichinweng9bbaa092018-12-20 15:03:37 +0800345 parse_packet_data(packet_data, packet_result)
346 else:
347 return False
348
349 return True
350
351
352#=======================================================================
353# Update and DumpData Function
354#=======================================================================
355
356
357def dump_audio_data(data):
358 """This function dumps audio data into file."""
359 file_type = "." + data[CODEC]
360 file_name_list = []
361 file_name_list.append(data[PEER_ADDRESS])
weichinweng49c14442019-04-19 17:27:32 +0800362 file_name_list.append(data[TIMESTAMP_STR_FORMAT])
weichinweng9bbaa092018-12-20 15:03:37 +0800363 file_name_list.append(data[AUDIO_TYPE])
364 file_name_list.append(data[SAMPLE_RATE])
365 if folder is not None:
366 if not os.path.exists(folder):
367 os.makedirs(folder)
weichinweng49c14442019-04-19 17:27:32 +0800368 audio_file_name = os.path.join(folder, "-".join(file_name_list) + file_type)
369 if data.has_key(DEBUG_VERSION):
370 file_prefix = "debug_ver_" + data[DEBUG_VERSION] + "-"
371 debug_file_name = os.path.join(folder, file_prefix + "-".join(file_name_list) + ".txt")
weichinweng9bbaa092018-12-20 15:03:37 +0800372 else:
weichinweng49c14442019-04-19 17:27:32 +0800373 audio_file_name = "-".join(file_name_list) + file_type
374 if data.has_key(DEBUG_VERSION):
375 file_prefix = "debug_ver_" + data[DEBUG_VERSION] + "-"
376 debug_file_name = file_prefix + "-".join(file_name_list) + ".txt"
377
378 sys.stdout.write("Start to dump Audio File : %s\n" % audio_file_name)
weichinweng9bbaa092018-12-20 15:03:37 +0800379 if data.has_key(AUDIO_DATA_B):
weichinweng49c14442019-04-19 17:27:32 +0800380 with open(audio_file_name, "wb+") as audio_file:
381 audio_file.write(data[AUDIO_DATA_B])
382 sys.stdout.write("Finished to dump Audio File: %s\n\n" % audio_file_name)
weichinweng9bbaa092018-12-20 15:03:37 +0800383 else:
weichinweng49c14442019-04-19 17:27:32 +0800384 sys.stdout.write("Fail to dump Audio File: %s\n" % audio_file_name)
weichinweng9bbaa092018-12-20 15:03:37 +0800385 sys.stdout.write("There isn't any Hearing Aid audio data.\n\n")
386
weichinweng49c14442019-04-19 17:27:32 +0800387 if simple_debug:
388 sys.stdout.write("Start to dump audio %s Debug File\n" % audio_file_name)
389 if data.has_key(DEBUG_DATA):
390 with open(debug_file_name, "wb+") as debug_file:
391 debug_file.write(data[DEBUG_DATA])
392 sys.stdout.write("Finished to dump Debug File: %s\n\n" % debug_file_name)
393 else:
394 sys.stdout.write("Fail to dump audio %s Debug File\n" % audio_file_name)
395 sys.stdout.write("There isn't any Hearing Aid debug data.\n\n")
396
397
weichinweng9bbaa092018-12-20 15:03:37 +0800398
399def update_audio_data(relate_key, relate_value, key, value):
400 """
401 This function records the dump audio file related information.
402 audio_data = {
403 PEER_ADDRESS:{
404 PEER_ADDRESS: PEER_ADDRESS,
405 CONNECTION_HANDLE: CONNECTION_HANDLE,
406 AUDIO_CONTROL_ATTR_HANDLE: AUDIO_CONTROL_ATTR_HANDLE,
407 START: True or False,
weichinweng49c14442019-04-19 17:27:32 +0800408 TIMESTAMP_STR_FORMAT: START_TIMESTAMP_STR_FORMAT,
weichinweng9bbaa092018-12-20 15:03:37 +0800409 CODEC: CODEC,
410 SAMPLE_RATE: SAMPLE_RATE,
411 AUDIO_TYPE: AUDIO_TYPE,
weichinweng49c14442019-04-19 17:27:32 +0800412 DEBUG_VERSION: DEBUG_VERSION,
413 DEBUG_DATA: DEBUG_DATA,
weichinweng9bbaa092018-12-20 15:03:37 +0800414 AUDIO_DATA_B: AUDIO_DATA_B
415 },
416 PEER_ADDRESS_2:{
417 PEER_ADDRESS: PEER_ADDRESS,
418 CONNECTION_HANDLE: CONNECTION_HANDLE,
419 AUDIO_CONTROL_ATTR_HANDLE: AUDIO_CONTROL_ATTR_HANDLE,
420 START: True or False,
weichinweng49c14442019-04-19 17:27:32 +0800421 TIMESTAMP_STR_FORMAT: START_TIMESTAMP_STR_FORMAT,
weichinweng9bbaa092018-12-20 15:03:37 +0800422 CODEC: CODEC,
423 SAMPLE_RATE: SAMPLE_RATE,
424 AUDIO_TYPE: AUDIO_TYPE,
weichinweng49c14442019-04-19 17:27:32 +0800425 DEBUG_VERSION: DEBUG_VERSION,
426 DEBUG_DATA: DEBUG_DATA,
weichinweng9bbaa092018-12-20 15:03:37 +0800427 AUDIO_DATA_B: AUDIO_DATA_B
428 }
429 }
430 """
431 if key == PEER_ADDRESS:
432 if audio_data.has_key(value):
433 # Dump audio data and clear previous data.
434 update_audio_data(key, value, START, False)
435 # Extra clear CONNECTION_HANDLE due to new connection create.
436 if audio_data[value].has_key(CONNECTION_HANDLE):
437 audio_data[value].pop(CONNECTION_HANDLE, "")
438 else:
439 device_audio_data = {key: value}
440 temp_audio_data = {value: device_audio_data}
441 audio_data.update(temp_audio_data)
442 else:
443 for i in audio_data:
444 if audio_data[i].has_key(relate_key) \
445 and audio_data[i][relate_key] == relate_value:
446 if key == START:
447 if audio_data[i].has_key(key) and audio_data[i][key]:
448 dump_audio_data(audio_data[i])
449 # Clear data except PEER_ADDRESS, CONNECTION_HANDLE and
450 # AUDIO_CONTROL_ATTR_HANDLE.
451 audio_data[i].pop(key, "")
weichinweng49c14442019-04-19 17:27:32 +0800452 audio_data[i].pop(TIMESTAMP_STR_FORMAT, "")
weichinweng9bbaa092018-12-20 15:03:37 +0800453 audio_data[i].pop(CODEC, "")
454 audio_data[i].pop(SAMPLE_RATE, "")
455 audio_data[i].pop(AUDIO_TYPE, "")
weichinweng49c14442019-04-19 17:27:32 +0800456 audio_data[i].pop(DEBUG_VERSION, "")
457 audio_data[i].pop(DEBUG_DATA, "")
weichinweng9bbaa092018-12-20 15:03:37 +0800458 audio_data[i].pop(AUDIO_DATA_B, "")
weichinweng49c14442019-04-19 17:27:32 +0800459 elif key == AUDIO_DATA_B or key == DEBUG_DATA:
weichinweng9bbaa092018-12-20 15:03:37 +0800460 if audio_data[i].has_key(START) and audio_data[i][START]:
weichinweng49c14442019-04-19 17:27:32 +0800461 if audio_data[i].has_key(key):
462 ori_data = audio_data[i].pop(key, "")
463 value = ori_data + value
weichinweng9bbaa092018-12-20 15:03:37 +0800464 else:
465 # Audio doesn't start, don't record.
466 return
467 device_audio_data = {key: value}
468 audio_data[i].update(device_audio_data)
469
470
471#=======================================================================
472# Tool Function
473#=======================================================================
474
475
476def get_audio_control_attr_handle(connection_handle):
477 """This function gets audio_control_attr_handle."""
478 # If force_audio_control_attr_handle is set, will use it first.
479 if force_audio_control_attr_handle is not None:
480 return force_audio_control_attr_handle
481
482 # Try to check the audio_control_attr_handle is record into audio_data.
483 for i in audio_data:
484 if audio_data[i].has_key(CONNECTION_HANDLE) \
485 and audio_data[i][CONNECTION_HANDLE] == connection_handle:
486 if audio_data[i].has_key(AUDIO_CONTROL_ATTR_HANDLE):
487 return audio_data[i][AUDIO_CONTROL_ATTR_HANDLE]
488
489 # Return default attr_handle if audio_data doesn't record it.
490 return default_audio_control_attr_handle
491
492
493def unpack_data(data, byte):
494 """This function unpacks data."""
495 if byte == 1:
496 value = struct.unpack(">B", data[0])[0]
497 elif byte == 2:
498 value = struct.unpack(">H", data[1]+data[0])[0]
499 else:
500 value = ""
501 data = data[byte:]
502 return value, data
503
504
505def convert_time_str(timestamp):
506 """This function converts time to string format."""
507 really_timestamp = float(timestamp) / SEC_CONVERT
508 local_timestamp = time.localtime(really_timestamp)
weichinweng9bbaa092018-12-20 15:03:37 +0800509 dt = really_timestamp - long(really_timestamp)
510 ms_str = "{0:06}".format(int(round(dt * 1000000)))
weichinweng49c14442019-04-19 17:27:32 +0800511
512 str_format = time.strftime("%m_%d__%H_%M_%S", local_timestamp)
513 full_str_format = str_format + "_" + ms_str
514
515 time_format = time.strftime("%m-%d %H:%M:%S", local_timestamp)
516 full_time_format = time_format + "." + ms_str
517 return full_str_format, full_time_format
weichinweng9bbaa092018-12-20 15:03:37 +0800518
519
520def set_config():
521 """This function is for set config by flag and check the argv is correct."""
522 argv_parser = argparse.ArgumentParser(
523 description="Extracts Hearing Aid audio data from BTSNOOP.")
524 argv_parser.add_argument("BTSNOOP", help="BLUETOOTH BTSNOOP file.")
525 argv_parser.add_argument("-f", "--folder", help="select output folder.",
526 dest="folder")
527 argv_parser.add_argument("-c1", "--connection-handle1",
528 help="set a fake connection handle 1 to capture \
529 audio dump.", dest="connection_handle1", type=int)
530 argv_parser.add_argument("-c2", "--connection-handle2",
531 help="set a fake connection handle 2 to capture \
532 audio dump.", dest="connection_handle2", type=int)
weichinweng5b58b1f2019-03-07 15:25:43 +0800533 argv_parser.add_argument("-ns", "--no-start", help="No audio 'Start' cmd is \
534 needed before extracting audio data.",
535 dest="no_start", default="False")
536 argv_parser.add_argument("-dc", "--default-codec", help="set a default \
537 codec.", dest="codec", default="G722")
weichinweng9bbaa092018-12-20 15:03:37 +0800538 argv_parser.add_argument("-a", "--attr-handle",
539 help="force to select audio control attr handle.",
540 dest="audio_control_attr_handle", type=int)
weichinweng49c14442019-04-19 17:27:32 +0800541 argv_parser.add_argument("-d", "--debug",
542 help="dump full debug buffer content.",
543 dest="full_debug", default="False")
544 argv_parser.add_argument("-sd", "--simple-debug",
545 help="dump debug buffer header content.",
546 dest="simple_debug", default="False")
weichinweng9bbaa092018-12-20 15:03:37 +0800547 arg = argv_parser.parse_args()
548
549 if arg.folder is not None:
550 global folder
551 folder = arg.folder
552
553 if arg.connection_handle1 is not None and arg.connection_handle2 is not None \
554 and arg.connection_handle1 == arg.connection_handle2:
555 argv_parser.error("connection_handle1 can't be same with \
556 connection_handle2")
557 exit(1)
558
weichinweng5b58b1f2019-03-07 15:25:43 +0800559 if not (arg.no_start.lower() == "true" or arg.no_start.lower() == "false"):
560 argv_parser.error("-ns/--no-start arg is invalid, it should be true/false.")
561 exit(1)
562
weichinweng9bbaa092018-12-20 15:03:37 +0800563 if arg.connection_handle1 is not None:
564 fake_name = "ConnectionHandle" + str(arg.connection_handle1)
565 update_audio_data("", "", PEER_ADDRESS, fake_name)
566 update_audio_data(PEER_ADDRESS, fake_name, CONNECTION_HANDLE,
567 arg.connection_handle1)
weichinweng5b58b1f2019-03-07 15:25:43 +0800568 if arg.no_start.lower() == "true":
569 update_audio_data(PEER_ADDRESS, fake_name, START, True)
weichinweng49c14442019-04-19 17:27:32 +0800570 update_audio_data(PEER_ADDRESS, fake_name, TIMESTAMP_STR_FORMAT, "Unknown")
weichinweng5b58b1f2019-03-07 15:25:43 +0800571 update_audio_data(PEER_ADDRESS, fake_name, CODEC, arg.codec)
572 update_audio_data(PEER_ADDRESS, fake_name, SAMPLE_RATE, "Unknown")
573 update_audio_data(PEER_ADDRESS, fake_name, AUDIO_TYPE, "Unknown")
weichinweng9bbaa092018-12-20 15:03:37 +0800574
575 if arg.connection_handle2 is not None:
576 fake_name = "ConnectionHandle" + str(arg.connection_handle2)
577 update_audio_data("", "", PEER_ADDRESS, fake_name)
578 update_audio_data(PEER_ADDRESS, fake_name, CONNECTION_HANDLE,
579 arg.connection_handle2)
weichinweng5b58b1f2019-03-07 15:25:43 +0800580 if arg.no_start.lower() == "true":
581 update_audio_data(PEER_ADDRESS, fake_name, START, True)
weichinweng49c14442019-04-19 17:27:32 +0800582 update_audio_data(PEER_ADDRESS, fake_name, TIMESTAMP_STR_FORMAT, "Unknown")
weichinweng5b58b1f2019-03-07 15:25:43 +0800583 update_audio_data(PEER_ADDRESS, fake_name, CODEC, arg.codec)
584 update_audio_data(PEER_ADDRESS, fake_name, SAMPLE_RATE, "Unknown")
585 update_audio_data(PEER_ADDRESS, fake_name, AUDIO_TYPE, "Unknown")
weichinweng9bbaa092018-12-20 15:03:37 +0800586
587 if arg.audio_control_attr_handle is not None:
588 global force_audio_control_attr_handle
589 force_audio_control_attr_handle = arg.audio_control_attr_handle
590
weichinweng49c14442019-04-19 17:27:32 +0800591 global full_debug
592 global simple_debug
593 if arg.full_debug.lower() == "true":
594 full_debug = True
595 simple_debug = True
596 elif arg.simple_debug.lower() == "true":
597 simple_debug = True
598
weichinweng9bbaa092018-12-20 15:03:37 +0800599 if os.path.isfile(arg.BTSNOOP):
600 return arg.BTSNOOP
601 else:
602 argv_parser.error("BTSNOOP file not found: %s" % arg.BTSNOOP)
603 exit(1)
604
605
606def main():
607 btsnoop_file_name = set_config()
608
609 with open(btsnoop_file_name, "rb") as btsnoop_file:
610 identification = btsnoop_file.read(8)
611 if identification != "btsnoop\0":
612 sys.stderr.write(
613 "Check identification fail. It is not correct btsnoop file.")
614 exit(1)
615
616 ver, data_link = struct.unpack(">II", btsnoop_file.read(4 + 4))
617 if (ver != 1) or (data_link != 1002):
618 sys.stderr.write(
619 "Check ver or dataLink fail. It is not correct btsnoop file.")
620 exit(1)
621
622 while True:
623 if not parse_packet(btsnoop_file):
624 break
625
626 for i in audio_data:
627 if audio_data[i].get(START, False):
628 dump_audio_data(audio_data[i])
629
630
631if __name__ == "__main__":
632 main()