bumble 0.0.199__py3-none-any.whl → 0.0.201__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bumble/_version.py +2 -2
- bumble/a2dp.py +502 -202
- bumble/apps/controller_info.py +60 -0
- bumble/apps/player/player.py +608 -0
- bumble/apps/speaker/speaker.py +25 -27
- bumble/att.py +2 -2
- bumble/avc.py +1 -2
- bumble/avdtp.py +54 -97
- bumble/avrcp.py +48 -29
- bumble/codecs.py +214 -68
- bumble/device.py +19 -11
- bumble/hci.py +31 -5
- bumble/hfp.py +52 -48
- bumble/host.py +12 -0
- bumble/profiles/hap.py +27 -18
- bumble/rtp.py +110 -0
- bumble/transport/android_netsim.py +31 -11
- bumble/transport/grpc_protobuf/netsim/__init__.py +0 -0
- bumble/transport/grpc_protobuf/{common_pb2.py → netsim/common_pb2.py} +9 -8
- bumble/transport/grpc_protobuf/{common_pb2.pyi → netsim/common_pb2.pyi} +11 -5
- bumble/transport/grpc_protobuf/netsim/hci_packet_pb2.py +29 -0
- bumble/transport/grpc_protobuf/{hci_packet_pb2.pyi → netsim/hci_packet_pb2.pyi} +13 -7
- bumble/transport/grpc_protobuf/netsim/model_pb2.py +63 -0
- bumble/transport/grpc_protobuf/netsim/model_pb2.pyi +238 -0
- bumble/transport/grpc_protobuf/netsim/packet_streamer_pb2.py +32 -0
- bumble/transport/grpc_protobuf/{packet_streamer_pb2.pyi → netsim/packet_streamer_pb2.pyi} +6 -6
- bumble/transport/grpc_protobuf/{packet_streamer_pb2_grpc.py → netsim/packet_streamer_pb2_grpc.py} +7 -7
- bumble/transport/grpc_protobuf/netsim/startup_pb2.py +41 -0
- bumble/transport/grpc_protobuf/netsim/startup_pb2.pyi +76 -0
- bumble/transport/grpc_protobuf/netsim/startup_pb2_grpc.py +4 -0
- bumble/transport/grpc_protobuf/rootcanal/__init__.py +0 -0
- bumble/transport/grpc_protobuf/rootcanal/configuration_pb2.py +39 -0
- bumble/transport/grpc_protobuf/rootcanal/configuration_pb2.pyi +78 -0
- bumble/transport/grpc_protobuf/rootcanal/configuration_pb2_grpc.py +4 -0
- bumble/transport/pyusb.py +2 -1
- {bumble-0.0.199.dist-info → bumble-0.0.201.dist-info}/METADATA +2 -2
- {bumble-0.0.199.dist-info → bumble-0.0.201.dist-info}/RECORD +44 -34
- {bumble-0.0.199.dist-info → bumble-0.0.201.dist-info}/WHEEL +1 -1
- {bumble-0.0.199.dist-info → bumble-0.0.201.dist-info}/entry_points.txt +1 -0
- bumble/transport/grpc_protobuf/hci_packet_pb2.py +0 -28
- bumble/transport/grpc_protobuf/packet_streamer_pb2.py +0 -31
- bumble/transport/grpc_protobuf/startup_pb2.py +0 -32
- bumble/transport/grpc_protobuf/startup_pb2.pyi +0 -46
- /bumble/transport/grpc_protobuf/{common_pb2_grpc.py → netsim/common_pb2_grpc.py} +0 -0
- /bumble/transport/grpc_protobuf/{hci_packet_pb2_grpc.py → netsim/hci_packet_pb2_grpc.py} +0 -0
- /bumble/transport/grpc_protobuf/{startup_pb2_grpc.py → netsim/model_pb2_grpc.py} +0 -0
- {bumble-0.0.199.dist-info → bumble-0.0.201.dist-info}/LICENSE +0 -0
- {bumble-0.0.199.dist-info → bumble-0.0.201.dist-info}/top_level.txt +0 -0
bumble/codecs.py
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
# -----------------------------------------------------------------------------
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
from dataclasses import dataclass
|
|
20
|
+
from typing_extensions import Self
|
|
20
21
|
|
|
21
22
|
from bumble import core
|
|
22
23
|
|
|
@@ -101,12 +102,40 @@ class BitReader:
|
|
|
101
102
|
break
|
|
102
103
|
|
|
103
104
|
|
|
105
|
+
# -----------------------------------------------------------------------------
|
|
106
|
+
class BitWriter:
|
|
107
|
+
"""Simple but not optimized bit stream writer."""
|
|
108
|
+
|
|
109
|
+
data: int
|
|
110
|
+
bit_count: int
|
|
111
|
+
|
|
112
|
+
def __init__(self) -> None:
|
|
113
|
+
self.data = 0
|
|
114
|
+
self.bit_count = 0
|
|
115
|
+
|
|
116
|
+
def write(self, value: int, bit_count: int) -> None:
|
|
117
|
+
self.data = (self.data << bit_count) | value
|
|
118
|
+
self.bit_count += bit_count
|
|
119
|
+
|
|
120
|
+
def write_bytes(self, data: bytes) -> None:
|
|
121
|
+
bit_count = 8 * len(data)
|
|
122
|
+
self.data = (self.data << bit_count) | int.from_bytes(data, 'big')
|
|
123
|
+
self.bit_count += bit_count
|
|
124
|
+
|
|
125
|
+
def __bytes__(self) -> bytes:
|
|
126
|
+
return (self.data << ((8 - (self.bit_count % 8)) % 8)).to_bytes(
|
|
127
|
+
(self.bit_count + 7) // 8, 'big'
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
|
|
104
131
|
# -----------------------------------------------------------------------------
|
|
105
132
|
class AacAudioRtpPacket:
|
|
106
133
|
"""AAC payload encapsulated in an RTP packet payload"""
|
|
107
134
|
|
|
135
|
+
audio_mux_element: AudioMuxElement
|
|
136
|
+
|
|
108
137
|
@staticmethod
|
|
109
|
-
def
|
|
138
|
+
def read_latm_value(reader: BitReader) -> int:
|
|
110
139
|
bytes_for_value = reader.read(2)
|
|
111
140
|
value = 0
|
|
112
141
|
for _ in range(bytes_for_value + 1):
|
|
@@ -114,24 +143,33 @@ class AacAudioRtpPacket:
|
|
|
114
143
|
return value
|
|
115
144
|
|
|
116
145
|
@staticmethod
|
|
117
|
-
def
|
|
118
|
-
|
|
146
|
+
def read_audio_object_type(reader: BitReader):
|
|
147
|
+
# GetAudioObjectType - ISO/EIC 14496-3 Table 1.16
|
|
148
|
+
audio_object_type = reader.read(5)
|
|
149
|
+
if audio_object_type == 31:
|
|
150
|
+
audio_object_type = 32 + reader.read(6)
|
|
151
|
+
|
|
152
|
+
return audio_object_type
|
|
119
153
|
|
|
120
154
|
@dataclass
|
|
121
155
|
class GASpecificConfig:
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
156
|
+
audio_object_type: int
|
|
157
|
+
# NOTE: other fields not supported
|
|
158
|
+
|
|
159
|
+
@classmethod
|
|
160
|
+
def from_bits(
|
|
161
|
+
cls, reader: BitReader, channel_configuration: int, audio_object_type: int
|
|
162
|
+
) -> Self:
|
|
125
163
|
# GASpecificConfig - ISO/EIC 14496-3 Table 4.1
|
|
126
164
|
frame_length_flag = reader.read(1)
|
|
127
165
|
depends_on_core_coder = reader.read(1)
|
|
128
166
|
if depends_on_core_coder:
|
|
129
|
-
|
|
167
|
+
core_coder_delay = reader.read(14)
|
|
130
168
|
extension_flag = reader.read(1)
|
|
131
169
|
if not channel_configuration:
|
|
132
|
-
|
|
170
|
+
raise core.InvalidPacketError('program_config_element not supported')
|
|
133
171
|
if audio_object_type in (6, 20):
|
|
134
|
-
|
|
172
|
+
layer_nr = reader.read(3)
|
|
135
173
|
if extension_flag:
|
|
136
174
|
if audio_object_type == 22:
|
|
137
175
|
num_of_sub_frame = reader.read(5)
|
|
@@ -144,14 +182,13 @@ class AacAudioRtpPacket:
|
|
|
144
182
|
if extension_flag_3 == 1:
|
|
145
183
|
raise core.InvalidPacketError('extensionFlag3 == 1 not supported')
|
|
146
184
|
|
|
147
|
-
|
|
148
|
-
def audio_object_type(reader: BitReader):
|
|
149
|
-
# GetAudioObjectType - ISO/EIC 14496-3 Table 1.16
|
|
150
|
-
audio_object_type = reader.read(5)
|
|
151
|
-
if audio_object_type == 31:
|
|
152
|
-
audio_object_type = 32 + reader.read(6)
|
|
185
|
+
return cls(audio_object_type)
|
|
153
186
|
|
|
154
|
-
|
|
187
|
+
def to_bits(self, writer: BitWriter) -> None:
|
|
188
|
+
assert self.audio_object_type in (1, 2)
|
|
189
|
+
writer.write(0, 1) # frame_length_flag = 0
|
|
190
|
+
writer.write(0, 1) # depends_on_core_coder = 0
|
|
191
|
+
writer.write(0, 1) # extension_flag = 0
|
|
155
192
|
|
|
156
193
|
@dataclass
|
|
157
194
|
class AudioSpecificConfig:
|
|
@@ -159,6 +196,7 @@ class AacAudioRtpPacket:
|
|
|
159
196
|
sampling_frequency_index: int
|
|
160
197
|
sampling_frequency: int
|
|
161
198
|
channel_configuration: int
|
|
199
|
+
ga_specific_config: AacAudioRtpPacket.GASpecificConfig
|
|
162
200
|
sbr_present_flag: int
|
|
163
201
|
ps_present_flag: int
|
|
164
202
|
extension_audio_object_type: int
|
|
@@ -182,44 +220,73 @@ class AacAudioRtpPacket:
|
|
|
182
220
|
7350,
|
|
183
221
|
]
|
|
184
222
|
|
|
185
|
-
|
|
223
|
+
@classmethod
|
|
224
|
+
def for_simple_aac(
|
|
225
|
+
cls,
|
|
226
|
+
audio_object_type: int,
|
|
227
|
+
sampling_frequency: int,
|
|
228
|
+
channel_configuration: int,
|
|
229
|
+
) -> Self:
|
|
230
|
+
if sampling_frequency not in cls.SAMPLING_FREQUENCIES:
|
|
231
|
+
raise ValueError(f'invalid sampling frequency {sampling_frequency}')
|
|
232
|
+
|
|
233
|
+
ga_specific_config = AacAudioRtpPacket.GASpecificConfig(audio_object_type)
|
|
234
|
+
|
|
235
|
+
return cls(
|
|
236
|
+
audio_object_type=audio_object_type,
|
|
237
|
+
sampling_frequency_index=cls.SAMPLING_FREQUENCIES.index(
|
|
238
|
+
sampling_frequency
|
|
239
|
+
),
|
|
240
|
+
sampling_frequency=sampling_frequency,
|
|
241
|
+
channel_configuration=channel_configuration,
|
|
242
|
+
ga_specific_config=ga_specific_config,
|
|
243
|
+
sbr_present_flag=0,
|
|
244
|
+
ps_present_flag=0,
|
|
245
|
+
extension_audio_object_type=0,
|
|
246
|
+
extension_sampling_frequency_index=0,
|
|
247
|
+
extension_sampling_frequency=0,
|
|
248
|
+
extension_channel_configuration=0,
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
@classmethod
|
|
252
|
+
def from_bits(cls, reader: BitReader) -> Self:
|
|
186
253
|
# AudioSpecificConfig - ISO/EIC 14496-3 Table 1.15
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
if
|
|
190
|
-
|
|
254
|
+
audio_object_type = AacAudioRtpPacket.read_audio_object_type(reader)
|
|
255
|
+
sampling_frequency_index = reader.read(4)
|
|
256
|
+
if sampling_frequency_index == 0xF:
|
|
257
|
+
sampling_frequency = reader.read(24)
|
|
191
258
|
else:
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
259
|
+
sampling_frequency = cls.SAMPLING_FREQUENCIES[sampling_frequency_index]
|
|
260
|
+
channel_configuration = reader.read(4)
|
|
261
|
+
sbr_present_flag = 0
|
|
262
|
+
ps_present_flag = 0
|
|
263
|
+
extension_sampling_frequency_index = 0
|
|
264
|
+
extension_sampling_frequency = 0
|
|
265
|
+
extension_channel_configuration = 0
|
|
266
|
+
extension_audio_object_type = 0
|
|
267
|
+
if audio_object_type in (5, 29):
|
|
268
|
+
extension_audio_object_type = 5
|
|
269
|
+
sbr_present_flag = 1
|
|
270
|
+
if audio_object_type == 29:
|
|
271
|
+
ps_present_flag = 1
|
|
272
|
+
extension_sampling_frequency_index = reader.read(4)
|
|
273
|
+
if extension_sampling_frequency_index == 0xF:
|
|
274
|
+
extension_sampling_frequency = reader.read(24)
|
|
206
275
|
else:
|
|
207
|
-
|
|
208
|
-
|
|
276
|
+
extension_sampling_frequency = cls.SAMPLING_FREQUENCIES[
|
|
277
|
+
extension_sampling_frequency_index
|
|
209
278
|
]
|
|
210
|
-
|
|
211
|
-
if
|
|
212
|
-
|
|
213
|
-
else:
|
|
214
|
-
self.extension_audio_object_type = 0
|
|
279
|
+
audio_object_type = AacAudioRtpPacket.read_audio_object_type(reader)
|
|
280
|
+
if audio_object_type == 22:
|
|
281
|
+
extension_channel_configuration = reader.read(4)
|
|
215
282
|
|
|
216
|
-
if
|
|
217
|
-
ga_specific_config = AacAudioRtpPacket.GASpecificConfig(
|
|
218
|
-
reader,
|
|
283
|
+
if audio_object_type in (1, 2, 3, 4, 6, 7, 17, 19, 20, 21, 22, 23):
|
|
284
|
+
ga_specific_config = AacAudioRtpPacket.GASpecificConfig.from_bits(
|
|
285
|
+
reader, channel_configuration, audio_object_type
|
|
219
286
|
)
|
|
220
287
|
else:
|
|
221
288
|
raise core.InvalidPacketError(
|
|
222
|
-
f'audioObjectType {
|
|
289
|
+
f'audioObjectType {audio_object_type} not supported'
|
|
223
290
|
)
|
|
224
291
|
|
|
225
292
|
# if self.extension_audio_object_type != 5 and bits_to_decode >= 16:
|
|
@@ -248,13 +315,44 @@ class AacAudioRtpPacket:
|
|
|
248
315
|
# self.extension_sampling_frequency = self.SAMPLING_FREQUENCIES[self.extension_sampling_frequency_index]
|
|
249
316
|
# self.extension_channel_configuration = reader.read(4)
|
|
250
317
|
|
|
318
|
+
return cls(
|
|
319
|
+
audio_object_type,
|
|
320
|
+
sampling_frequency_index,
|
|
321
|
+
sampling_frequency,
|
|
322
|
+
channel_configuration,
|
|
323
|
+
ga_specific_config,
|
|
324
|
+
sbr_present_flag,
|
|
325
|
+
ps_present_flag,
|
|
326
|
+
extension_audio_object_type,
|
|
327
|
+
extension_sampling_frequency_index,
|
|
328
|
+
extension_sampling_frequency,
|
|
329
|
+
extension_channel_configuration,
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
def to_bits(self, writer: BitWriter) -> None:
|
|
333
|
+
if self.sampling_frequency_index >= 15:
|
|
334
|
+
raise ValueError(
|
|
335
|
+
f"unsupported sampling frequency index {self.sampling_frequency_index}"
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
if self.audio_object_type not in (1, 2):
|
|
339
|
+
raise ValueError(
|
|
340
|
+
f"unsupported audio object type {self.audio_object_type} "
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
writer.write(self.audio_object_type, 5)
|
|
344
|
+
writer.write(self.sampling_frequency_index, 4)
|
|
345
|
+
writer.write(self.channel_configuration, 4)
|
|
346
|
+
self.ga_specific_config.to_bits(writer)
|
|
347
|
+
|
|
251
348
|
@dataclass
|
|
252
349
|
class StreamMuxConfig:
|
|
253
350
|
other_data_present: int
|
|
254
351
|
other_data_len_bits: int
|
|
255
352
|
audio_specific_config: AacAudioRtpPacket.AudioSpecificConfig
|
|
256
353
|
|
|
257
|
-
|
|
354
|
+
@classmethod
|
|
355
|
+
def from_bits(cls, reader: BitReader) -> Self:
|
|
258
356
|
# StreamMuxConfig - ISO/EIC 14496-3 Table 1.42
|
|
259
357
|
audio_mux_version = reader.read(1)
|
|
260
358
|
if audio_mux_version == 1:
|
|
@@ -264,7 +362,7 @@ class AacAudioRtpPacket:
|
|
|
264
362
|
if audio_mux_version_a != 0:
|
|
265
363
|
raise core.InvalidPacketError('audioMuxVersionA != 0 not supported')
|
|
266
364
|
if audio_mux_version == 1:
|
|
267
|
-
tara_buffer_fullness = AacAudioRtpPacket.
|
|
365
|
+
tara_buffer_fullness = AacAudioRtpPacket.read_latm_value(reader)
|
|
268
366
|
stream_cnt = 0
|
|
269
367
|
all_streams_same_time_framing = reader.read(1)
|
|
270
368
|
num_sub_frames = reader.read(6)
|
|
@@ -275,13 +373,13 @@ class AacAudioRtpPacket:
|
|
|
275
373
|
if num_layer != 0:
|
|
276
374
|
raise core.InvalidPacketError('num_layer != 0 not supported')
|
|
277
375
|
if audio_mux_version == 0:
|
|
278
|
-
|
|
376
|
+
audio_specific_config = AacAudioRtpPacket.AudioSpecificConfig.from_bits(
|
|
279
377
|
reader
|
|
280
378
|
)
|
|
281
379
|
else:
|
|
282
|
-
asc_len = AacAudioRtpPacket.
|
|
380
|
+
asc_len = AacAudioRtpPacket.read_latm_value(reader)
|
|
283
381
|
marker = reader.bit_position
|
|
284
|
-
|
|
382
|
+
audio_specific_config = AacAudioRtpPacket.AudioSpecificConfig.from_bits(
|
|
285
383
|
reader
|
|
286
384
|
)
|
|
287
385
|
audio_specific_config_len = reader.bit_position - marker
|
|
@@ -299,36 +397,49 @@ class AacAudioRtpPacket:
|
|
|
299
397
|
f'frame_length_type {frame_length_type} not supported'
|
|
300
398
|
)
|
|
301
399
|
|
|
302
|
-
|
|
303
|
-
|
|
400
|
+
other_data_present = reader.read(1)
|
|
401
|
+
other_data_len_bits = 0
|
|
402
|
+
if other_data_present:
|
|
304
403
|
if audio_mux_version == 1:
|
|
305
|
-
|
|
404
|
+
other_data_len_bits = AacAudioRtpPacket.read_latm_value(reader)
|
|
306
405
|
else:
|
|
307
|
-
self.other_data_len_bits = 0
|
|
308
406
|
while True:
|
|
309
|
-
|
|
407
|
+
other_data_len_bits *= 256
|
|
310
408
|
other_data_len_esc = reader.read(1)
|
|
311
|
-
|
|
409
|
+
other_data_len_bits += reader.read(8)
|
|
312
410
|
if other_data_len_esc == 0:
|
|
313
411
|
break
|
|
314
412
|
crc_check_present = reader.read(1)
|
|
315
413
|
if crc_check_present:
|
|
316
414
|
crc_checksum = reader.read(8)
|
|
317
415
|
|
|
416
|
+
return cls(other_data_present, other_data_len_bits, audio_specific_config)
|
|
417
|
+
|
|
418
|
+
def to_bits(self, writer: BitWriter) -> None:
|
|
419
|
+
writer.write(0, 1) # audioMuxVersion = 0
|
|
420
|
+
writer.write(1, 1) # allStreamsSameTimeFraming = 1
|
|
421
|
+
writer.write(0, 6) # numSubFrames = 0
|
|
422
|
+
writer.write(0, 4) # numProgram = 0
|
|
423
|
+
writer.write(0, 3) # numLayer = 0
|
|
424
|
+
self.audio_specific_config.to_bits(writer)
|
|
425
|
+
writer.write(0, 3) # frameLengthType = 0
|
|
426
|
+
writer.write(0, 8) # latmBufferFullness = 0
|
|
427
|
+
writer.write(0, 1) # otherDataPresent = 0
|
|
428
|
+
writer.write(0, 1) # crcCheckPresent = 0
|
|
429
|
+
|
|
318
430
|
@dataclass
|
|
319
431
|
class AudioMuxElement:
|
|
320
|
-
payload: bytes
|
|
321
432
|
stream_mux_config: AacAudioRtpPacket.StreamMuxConfig
|
|
433
|
+
payload: bytes
|
|
322
434
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
raise core.InvalidPacketError('muxConfigPresent == 0 not supported')
|
|
326
|
-
|
|
435
|
+
@classmethod
|
|
436
|
+
def from_bits(cls, reader: BitReader) -> Self:
|
|
327
437
|
# AudioMuxElement - ISO/EIC 14496-3 Table 1.41
|
|
438
|
+
# (only supports mux_config_present=1)
|
|
328
439
|
use_same_stream_mux = reader.read(1)
|
|
329
440
|
if use_same_stream_mux:
|
|
330
441
|
raise core.InvalidPacketError('useSameStreamMux == 1 not supported')
|
|
331
|
-
|
|
442
|
+
stream_mux_config = AacAudioRtpPacket.StreamMuxConfig.from_bits(reader)
|
|
332
443
|
|
|
333
444
|
# We only support:
|
|
334
445
|
# allStreamsSameTimeFraming == 1
|
|
@@ -344,19 +455,46 @@ class AacAudioRtpPacket:
|
|
|
344
455
|
if tmp != 255:
|
|
345
456
|
break
|
|
346
457
|
|
|
347
|
-
|
|
458
|
+
payload = reader.read_bytes(mux_slot_length_bytes)
|
|
348
459
|
|
|
349
|
-
if
|
|
350
|
-
reader.skip(
|
|
460
|
+
if stream_mux_config.other_data_present:
|
|
461
|
+
reader.skip(stream_mux_config.other_data_len_bits)
|
|
351
462
|
|
|
352
463
|
# ByteAlign
|
|
353
464
|
while reader.bit_position % 8:
|
|
354
465
|
reader.read(1)
|
|
355
466
|
|
|
356
|
-
|
|
467
|
+
return cls(stream_mux_config, payload)
|
|
468
|
+
|
|
469
|
+
def to_bits(self, writer: BitWriter) -> None:
|
|
470
|
+
writer.write(0, 1) # useSameStreamMux = 0
|
|
471
|
+
self.stream_mux_config.to_bits(writer)
|
|
472
|
+
mux_slot_length_bytes = len(self.payload)
|
|
473
|
+
while mux_slot_length_bytes > 255:
|
|
474
|
+
writer.write(255, 8)
|
|
475
|
+
mux_slot_length_bytes -= 255
|
|
476
|
+
writer.write(mux_slot_length_bytes, 8)
|
|
477
|
+
if mux_slot_length_bytes == 255:
|
|
478
|
+
writer.write(0, 8)
|
|
479
|
+
writer.write_bytes(self.payload)
|
|
480
|
+
|
|
481
|
+
@classmethod
|
|
482
|
+
def from_bytes(cls, data: bytes) -> Self:
|
|
357
483
|
# Parse the bit stream
|
|
358
484
|
reader = BitReader(data)
|
|
359
|
-
|
|
485
|
+
return cls(cls.AudioMuxElement.from_bits(reader))
|
|
486
|
+
|
|
487
|
+
@classmethod
|
|
488
|
+
def for_simple_aac(
|
|
489
|
+
cls, sampling_frequency: int, channel_configuration: int, payload: bytes
|
|
490
|
+
) -> Self:
|
|
491
|
+
audio_specific_config = cls.AudioSpecificConfig.for_simple_aac(
|
|
492
|
+
2, sampling_frequency, channel_configuration
|
|
493
|
+
)
|
|
494
|
+
stream_mux_config = cls.StreamMuxConfig(0, 0, audio_specific_config)
|
|
495
|
+
audio_mux_element = cls.AudioMuxElement(stream_mux_config, payload)
|
|
496
|
+
|
|
497
|
+
return cls(audio_mux_element)
|
|
360
498
|
|
|
361
499
|
def to_adts(self):
|
|
362
500
|
# pylint: disable=line-too-long
|
|
@@ -383,3 +521,11 @@ class AacAudioRtpPacket:
|
|
|
383
521
|
)
|
|
384
522
|
+ self.audio_mux_element.payload
|
|
385
523
|
)
|
|
524
|
+
|
|
525
|
+
def __init__(self, audio_mux_element: AudioMuxElement) -> None:
|
|
526
|
+
self.audio_mux_element = audio_mux_element
|
|
527
|
+
|
|
528
|
+
def __bytes__(self) -> bytes:
|
|
529
|
+
writer = BitWriter()
|
|
530
|
+
self.audio_mux_element.to_bits(writer)
|
|
531
|
+
return bytes(writer)
|
bumble/device.py
CHANGED
|
@@ -1571,14 +1571,22 @@ class Connection(CompositeEventEmitter):
|
|
|
1571
1571
|
raise
|
|
1572
1572
|
|
|
1573
1573
|
def __str__(self):
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1574
|
+
if self.transport == BT_LE_TRANSPORT:
|
|
1575
|
+
return (
|
|
1576
|
+
f'Connection(transport=LE, handle=0x{self.handle:04X}, '
|
|
1577
|
+
f'role={self.role_name}, '
|
|
1578
|
+
f'self_address={self.self_address}, '
|
|
1579
|
+
f'self_resolvable_address={self.self_resolvable_address}, '
|
|
1580
|
+
f'peer_address={self.peer_address}, '
|
|
1581
|
+
f'peer_resolvable_address={self.peer_resolvable_address})'
|
|
1582
|
+
)
|
|
1583
|
+
else:
|
|
1584
|
+
return (
|
|
1585
|
+
f'Connection(transport=BR/EDR, handle=0x{self.handle:04X}, '
|
|
1586
|
+
f'role={self.role_name}, '
|
|
1587
|
+
f'self_address={self.self_address}, '
|
|
1588
|
+
f'peer_address={self.peer_address})'
|
|
1589
|
+
)
|
|
1582
1590
|
|
|
1583
1591
|
|
|
1584
1592
|
# -----------------------------------------------------------------------------
|
|
@@ -1766,9 +1774,9 @@ device_host_event_handlers: List[str] = []
|
|
|
1766
1774
|
# -----------------------------------------------------------------------------
|
|
1767
1775
|
class Device(CompositeEventEmitter):
|
|
1768
1776
|
# Incomplete list of fields.
|
|
1769
|
-
random_address: Address # Random address that may change
|
|
1770
|
-
public_address: Address # Public address (
|
|
1771
|
-
static_address: Address # Random address that
|
|
1777
|
+
random_address: Address # Random private address that may change periodically
|
|
1778
|
+
public_address: Address # Public address that is globally unique (from controller)
|
|
1779
|
+
static_address: Address # Random static address that does not change once set
|
|
1772
1780
|
classic_enabled: bool
|
|
1773
1781
|
name: str
|
|
1774
1782
|
class_of_device: int
|
bumble/hci.py
CHANGED
|
@@ -3440,11 +3440,11 @@ class HCI_Read_Local_Supported_Codecs_V2_Command(HCI_Command):
|
|
|
3440
3440
|
See Bluetooth spec @ 7.4.8 Read Local Supported Codecs Command
|
|
3441
3441
|
'''
|
|
3442
3442
|
|
|
3443
|
-
class Transport(
|
|
3444
|
-
BR_EDR_ACL =
|
|
3445
|
-
BR_EDR_SCO =
|
|
3446
|
-
LE_CIS =
|
|
3447
|
-
LE_BIS =
|
|
3443
|
+
class Transport(enum.IntFlag):
|
|
3444
|
+
BR_EDR_ACL = 1 << 0
|
|
3445
|
+
BR_EDR_SCO = 1 << 1
|
|
3446
|
+
LE_CIS = 1 << 2
|
|
3447
|
+
LE_BIS = 1 << 3
|
|
3448
3448
|
|
|
3449
3449
|
|
|
3450
3450
|
# -----------------------------------------------------------------------------
|
|
@@ -6065,6 +6065,32 @@ class HCI_Read_Remote_Version_Information_Complete_Event(HCI_Event):
|
|
|
6065
6065
|
'''
|
|
6066
6066
|
|
|
6067
6067
|
|
|
6068
|
+
# -----------------------------------------------------------------------------
|
|
6069
|
+
@HCI_Event.event(
|
|
6070
|
+
[
|
|
6071
|
+
('status', STATUS_SPEC),
|
|
6072
|
+
('connection_handle', 2),
|
|
6073
|
+
('unused', 1),
|
|
6074
|
+
(
|
|
6075
|
+
'service_type',
|
|
6076
|
+
{
|
|
6077
|
+
'size': 1,
|
|
6078
|
+
'mapper': lambda x: HCI_QOS_Setup_Complete_Event.ServiceType(x).name,
|
|
6079
|
+
},
|
|
6080
|
+
),
|
|
6081
|
+
]
|
|
6082
|
+
)
|
|
6083
|
+
class HCI_QOS_Setup_Complete_Event(HCI_Event):
|
|
6084
|
+
'''
|
|
6085
|
+
See Bluetooth spec @ 7.7.13 QoS Setup Complete Event
|
|
6086
|
+
'''
|
|
6087
|
+
|
|
6088
|
+
class ServiceType(OpenIntEnum):
|
|
6089
|
+
NO_TRAFFIC_AVAILABLE = 0x00
|
|
6090
|
+
BEST_EFFORT_AVAILABLE = 0x01
|
|
6091
|
+
GUARANTEED_AVAILABLE = 0x02
|
|
6092
|
+
|
|
6093
|
+
|
|
6068
6094
|
# -----------------------------------------------------------------------------
|
|
6069
6095
|
@HCI_Event.event(
|
|
6070
6096
|
[
|
bumble/hfp.py
CHANGED
|
@@ -795,29 +795,32 @@ class HfProtocol(pyee.EventEmitter):
|
|
|
795
795
|
# Append to the read buffer.
|
|
796
796
|
self.read_buffer.extend(data)
|
|
797
797
|
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
798
|
+
while self.read_buffer:
|
|
799
|
+
# Locate header and trailer.
|
|
800
|
+
header = self.read_buffer.find(b'\r\n')
|
|
801
|
+
trailer = self.read_buffer.find(b'\r\n', header + 2)
|
|
802
|
+
if header == -1 or trailer == -1:
|
|
803
|
+
return
|
|
804
|
+
|
|
805
|
+
# Isolate the AT response code and parameters.
|
|
806
|
+
raw_response = self.read_buffer[header + 2 : trailer]
|
|
807
|
+
response = AtResponse.parse_from(raw_response)
|
|
808
|
+
logger.debug(f"<<< {raw_response.decode()}")
|
|
809
|
+
|
|
810
|
+
# Consume the response bytes.
|
|
811
|
+
self.read_buffer = self.read_buffer[trailer + 2 :]
|
|
812
|
+
|
|
813
|
+
# Forward the received code to the correct queue.
|
|
814
|
+
if self.command_lock.locked() and (
|
|
815
|
+
response.code in STATUS_CODES or response.code in RESPONSE_CODES
|
|
816
|
+
):
|
|
817
|
+
self.response_queue.put_nowait(response)
|
|
818
|
+
elif response.code in UNSOLICITED_CODES:
|
|
819
|
+
self.unsolicited_queue.put_nowait(response)
|
|
820
|
+
else:
|
|
821
|
+
logger.warning(
|
|
822
|
+
f"dropping unexpected response with code '{response.code}'"
|
|
823
|
+
)
|
|
821
824
|
|
|
822
825
|
async def execute_command(
|
|
823
826
|
self,
|
|
@@ -1244,31 +1247,32 @@ class AgProtocol(pyee.EventEmitter):
|
|
|
1244
1247
|
# Append to the read buffer.
|
|
1245
1248
|
self.read_buffer.extend(data)
|
|
1246
1249
|
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
handler(
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1250
|
+
while self.read_buffer:
|
|
1251
|
+
# Locate the trailer.
|
|
1252
|
+
trailer = self.read_buffer.find(b'\r')
|
|
1253
|
+
if trailer == -1:
|
|
1254
|
+
return
|
|
1255
|
+
|
|
1256
|
+
# Isolate the AT response code and parameters.
|
|
1257
|
+
raw_command = self.read_buffer[:trailer]
|
|
1258
|
+
command = AtCommand.parse_from(raw_command)
|
|
1259
|
+
logger.debug(f"<<< {raw_command.decode()}")
|
|
1260
|
+
|
|
1261
|
+
# Consume the response bytes.
|
|
1262
|
+
self.read_buffer = self.read_buffer[trailer + 1 :]
|
|
1263
|
+
|
|
1264
|
+
if command.sub_code == AtCommand.SubCode.TEST:
|
|
1265
|
+
handler_name = f'_on_{command.code.lower()}_test'
|
|
1266
|
+
elif command.sub_code == AtCommand.SubCode.READ:
|
|
1267
|
+
handler_name = f'_on_{command.code.lower()}_read'
|
|
1268
|
+
else:
|
|
1269
|
+
handler_name = f'_on_{command.code.lower()}'
|
|
1270
|
+
|
|
1271
|
+
if handler := getattr(self, handler_name, None):
|
|
1272
|
+
handler(*command.parameters)
|
|
1273
|
+
else:
|
|
1274
|
+
logger.warning('Handler %s not found', handler_name)
|
|
1275
|
+
self.send_response('ERROR')
|
|
1272
1276
|
|
|
1273
1277
|
def send_response(self, response: str) -> None:
|
|
1274
1278
|
"""Sends an AT response."""
|
bumble/host.py
CHANGED
|
@@ -1106,6 +1106,18 @@ class Host(AbortableEventEmitter):
|
|
|
1106
1106
|
event.status,
|
|
1107
1107
|
)
|
|
1108
1108
|
|
|
1109
|
+
def on_hci_qos_setup_complete_event(self, event):
|
|
1110
|
+
if event.status == hci.HCI_SUCCESS:
|
|
1111
|
+
self.emit(
|
|
1112
|
+
'connection_qos_setup', event.connection_handle, event.service_type
|
|
1113
|
+
)
|
|
1114
|
+
else:
|
|
1115
|
+
self.emit(
|
|
1116
|
+
'connection_qos_setup_failure',
|
|
1117
|
+
event.connection_handle,
|
|
1118
|
+
event.status,
|
|
1119
|
+
)
|
|
1120
|
+
|
|
1109
1121
|
def on_hci_link_supervision_timeout_changed_event(self, event):
|
|
1110
1122
|
pass
|
|
1111
1123
|
|