bumble 0.0.155__py3-none-any.whl → 0.0.156__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/avdtp.py CHANGED
@@ -1207,7 +1207,7 @@ class DelayReport_Reject(Simple_Reject):
1207
1207
 
1208
1208
 
1209
1209
  # -----------------------------------------------------------------------------
1210
- class Protocol:
1210
+ class Protocol(EventEmitter):
1211
1211
  SINGLE_PACKET = 0
1212
1212
  START_PACKET = 1
1213
1213
  CONTINUE_PACKET = 2
@@ -1234,6 +1234,7 @@ class Protocol:
1234
1234
  return protocol
1235
1235
 
1236
1236
  def __init__(self, l2cap_channel, version=(1, 3)):
1237
+ super().__init__()
1237
1238
  self.l2cap_channel = l2cap_channel
1238
1239
  self.version = version
1239
1240
  self.rtx_sig_timer = AVDTP_DEFAULT_RTX_SIG_TIMER
@@ -1250,6 +1251,7 @@ class Protocol:
1250
1251
  # Register to receive PDUs from the channel
1251
1252
  l2cap_channel.sink = self.on_pdu
1252
1253
  l2cap_channel.on('open', self.on_l2cap_channel_open)
1254
+ l2cap_channel.on('close', self.on_l2cap_channel_close)
1253
1255
 
1254
1256
  def get_local_endpoint_by_seid(self, seid):
1255
1257
  if 0 < seid <= len(self.local_endpoints):
@@ -1392,11 +1394,18 @@ class Protocol:
1392
1394
 
1393
1395
  def on_l2cap_connection(self, channel):
1394
1396
  # Forward the channel to the endpoint that's expecting it
1395
- if self.channel_acceptor:
1396
- self.channel_acceptor.on_l2cap_connection(channel)
1397
+ if self.channel_acceptor is None:
1398
+ logger.warning(color('!!! l2cap connection with no acceptor', 'red'))
1399
+ return
1400
+ self.channel_acceptor.on_l2cap_connection(channel)
1397
1401
 
1398
1402
  def on_l2cap_channel_open(self):
1399
1403
  logger.debug(color('<<< L2CAP channel open', 'magenta'))
1404
+ self.emit('open')
1405
+
1406
+ def on_l2cap_channel_close(self):
1407
+ logger.debug(color('<<< L2CAP channel close', 'magenta'))
1408
+ self.emit('close')
1400
1409
 
1401
1410
  def send_message(self, transaction_label, message):
1402
1411
  logger.debug(
@@ -1651,6 +1660,10 @@ class Listener(EventEmitter):
1651
1660
  def set_server(self, connection, server):
1652
1661
  self.servers[connection.handle] = server
1653
1662
 
1663
+ def remove_server(self, connection):
1664
+ if connection.handle in self.servers:
1665
+ del self.servers[connection.handle]
1666
+
1654
1667
  def __init__(self, registrar, version=(1, 3)):
1655
1668
  super().__init__()
1656
1669
  self.version = version
@@ -1669,11 +1682,17 @@ class Listener(EventEmitter):
1669
1682
  else:
1670
1683
  # This is a new command/response channel
1671
1684
  def on_channel_open():
1685
+ logger.debug('setting up new Protocol for the connection')
1672
1686
  server = Protocol(channel, self.version)
1673
1687
  self.set_server(channel.connection, server)
1674
1688
  self.emit('connection', server)
1675
1689
 
1690
+ def on_channel_close():
1691
+ logger.debug('removing Protocol for the connection')
1692
+ self.remove_server(channel.connection)
1693
+
1676
1694
  channel.on('open', on_channel_open)
1695
+ channel.on('close', on_channel_close)
1677
1696
 
1678
1697
 
1679
1698
  # -----------------------------------------------------------------------------
@@ -1967,11 +1986,12 @@ class DiscoveredStreamEndPoint(StreamEndPoint, StreamEndPointProxy):
1967
1986
 
1968
1987
 
1969
1988
  # -----------------------------------------------------------------------------
1970
- class LocalStreamEndPoint(StreamEndPoint):
1989
+ class LocalStreamEndPoint(StreamEndPoint, EventEmitter):
1971
1990
  def __init__(
1972
1991
  self, protocol, seid, media_type, tsep, capabilities, configuration=None
1973
1992
  ):
1974
- super().__init__(seid, media_type, tsep, 0, capabilities)
1993
+ StreamEndPoint.__init__(self, seid, media_type, tsep, 0, capabilities)
1994
+ EventEmitter.__init__(self)
1975
1995
  self.protocol = protocol
1976
1996
  self.configuration = configuration if configuration is not None else []
1977
1997
  self.stream = None
@@ -1988,40 +2008,47 @@ class LocalStreamEndPoint(StreamEndPoint):
1988
2008
  def on_reconfigure_command(self, command):
1989
2009
  pass
1990
2010
 
2011
+ def on_set_configuration_command(self, configuration):
2012
+ logger.debug(
2013
+ '<<< received configuration: '
2014
+ f'{",".join([str(capability) for capability in configuration])}'
2015
+ )
2016
+ self.configuration = configuration
2017
+ self.emit('configuration')
2018
+
1991
2019
  def on_get_configuration_command(self):
1992
2020
  return Get_Configuration_Response(self.configuration)
1993
2021
 
1994
2022
  def on_open_command(self):
1995
- pass
2023
+ self.emit('open')
1996
2024
 
1997
2025
  def on_start_command(self):
1998
- pass
2026
+ self.emit('start')
1999
2027
 
2000
2028
  def on_suspend_command(self):
2001
- pass
2029
+ self.emit('suspend')
2002
2030
 
2003
2031
  def on_close_command(self):
2004
- pass
2032
+ self.emit('close')
2005
2033
 
2006
2034
  def on_abort_command(self):
2007
- pass
2035
+ self.emit('abort')
2008
2036
 
2009
2037
  def on_rtp_channel_open(self):
2010
- pass
2038
+ self.emit('rtp_channel_open')
2011
2039
 
2012
2040
  def on_rtp_channel_close(self):
2013
- pass
2041
+ self.emit('rtp_channel_close')
2014
2042
 
2015
2043
 
2016
2044
  # -----------------------------------------------------------------------------
2017
- class LocalSource(LocalStreamEndPoint, EventEmitter):
2045
+ class LocalSource(LocalStreamEndPoint):
2018
2046
  def __init__(self, protocol, seid, codec_capabilities, packet_pump):
2019
2047
  capabilities = [
2020
2048
  ServiceCapabilities(AVDTP_MEDIA_TRANSPORT_SERVICE_CATEGORY),
2021
2049
  codec_capabilities,
2022
2050
  ]
2023
- LocalStreamEndPoint.__init__(
2024
- self,
2051
+ super().__init__(
2025
2052
  protocol,
2026
2053
  seid,
2027
2054
  codec_capabilities.media_type,
@@ -2029,14 +2056,13 @@ class LocalSource(LocalStreamEndPoint, EventEmitter):
2029
2056
  capabilities,
2030
2057
  capabilities,
2031
2058
  )
2032
- EventEmitter.__init__(self)
2033
2059
  self.packet_pump = packet_pump
2034
2060
 
2035
2061
  async def start(self):
2036
2062
  if self.packet_pump:
2037
2063
  return await self.packet_pump.start(self.stream.rtp_channel)
2038
2064
 
2039
- self.emit('start', self.stream.rtp_channel)
2065
+ self.emit('start')
2040
2066
 
2041
2067
  async def stop(self):
2042
2068
  if self.packet_pump:
@@ -2044,11 +2070,6 @@ class LocalSource(LocalStreamEndPoint, EventEmitter):
2044
2070
 
2045
2071
  self.emit('stop')
2046
2072
 
2047
- def on_set_configuration_command(self, configuration):
2048
- # For now, blindly accept the configuration
2049
- logger.debug(f'<<< received source configuration: {configuration}')
2050
- self.configuration = configuration
2051
-
2052
2073
  def on_start_command(self):
2053
2074
  asyncio.create_task(self.start())
2054
2075
 
@@ -2057,30 +2078,28 @@ class LocalSource(LocalStreamEndPoint, EventEmitter):
2057
2078
 
2058
2079
 
2059
2080
  # -----------------------------------------------------------------------------
2060
- class LocalSink(LocalStreamEndPoint, EventEmitter):
2081
+ class LocalSink(LocalStreamEndPoint):
2061
2082
  def __init__(self, protocol, seid, codec_capabilities):
2062
2083
  capabilities = [
2063
2084
  ServiceCapabilities(AVDTP_MEDIA_TRANSPORT_SERVICE_CATEGORY),
2064
2085
  codec_capabilities,
2065
2086
  ]
2066
- LocalStreamEndPoint.__init__(
2067
- self,
2087
+ super().__init__(
2068
2088
  protocol,
2069
2089
  seid,
2070
2090
  codec_capabilities.media_type,
2071
2091
  AVDTP_TSEP_SNK,
2072
2092
  capabilities,
2073
2093
  )
2074
- EventEmitter.__init__(self)
2075
-
2076
- def on_set_configuration_command(self, configuration):
2077
- # For now, blindly accept the configuration
2078
- logger.debug(f'<<< received sink configuration: {configuration}')
2079
- self.configuration = configuration
2080
2094
 
2081
2095
  def on_rtp_channel_open(self):
2082
2096
  logger.debug(color('<<< RTP channel open', 'magenta'))
2083
2097
  self.stream.rtp_channel.sink = self.on_avdtp_packet
2098
+ super().on_rtp_channel_open()
2099
+
2100
+ def on_rtp_channel_close(self):
2101
+ logger.debug(color('<<< RTP channel close', 'magenta'))
2102
+ super().on_rtp_channel_close()
2084
2103
 
2085
2104
  def on_avdtp_packet(self, packet):
2086
2105
  rtp_packet = MediaPacket.from_bytes(packet)
bumble/codecs.py ADDED
@@ -0,0 +1,381 @@
1
+ # Copyright 2023 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # -----------------------------------------------------------------------------
16
+ # Imports
17
+ # -----------------------------------------------------------------------------
18
+ from __future__ import annotations
19
+ from dataclasses import dataclass
20
+
21
+
22
+ # -----------------------------------------------------------------------------
23
+ class BitReader:
24
+ """Simple but not optimized bit stream reader."""
25
+
26
+ data: bytes
27
+ bytes_position: int
28
+ bit_position: int
29
+ cache: int
30
+ bits_cached: int
31
+
32
+ def __init__(self, data: bytes):
33
+ self.data = data
34
+ self.byte_position = 0
35
+ self.bit_position = 0
36
+ self.cache = 0
37
+ self.bits_cached = 0
38
+
39
+ def read(self, bits: int) -> int:
40
+ """ "Read up to 32 bits."""
41
+
42
+ if bits > 32:
43
+ raise ValueError('maximum read size is 32')
44
+
45
+ if self.bits_cached >= bits:
46
+ # We have enough bits.
47
+ self.bits_cached -= bits
48
+ self.bit_position += bits
49
+ return (self.cache >> self.bits_cached) & ((1 << bits) - 1)
50
+
51
+ # Read more cache, up to 32 bits
52
+ feed_bytes = self.data[self.byte_position : self.byte_position + 4]
53
+ feed_size = len(feed_bytes)
54
+ feed_int = int.from_bytes(feed_bytes, byteorder='big')
55
+ if 8 * feed_size + self.bits_cached < bits:
56
+ raise ValueError('trying to read past the data')
57
+ self.byte_position += feed_size
58
+
59
+ # Combine the new cache and the old cache
60
+ cache = self.cache & ((1 << self.bits_cached) - 1)
61
+ new_bits = bits - self.bits_cached
62
+ self.bits_cached = 8 * feed_size - new_bits
63
+ result = (feed_int >> self.bits_cached) | (cache << new_bits)
64
+ self.cache = feed_int
65
+
66
+ self.bit_position += bits
67
+ return result
68
+
69
+ def read_bytes(self, count: int):
70
+ if self.bit_position + 8 * count > 8 * len(self.data):
71
+ raise ValueError('not enough data')
72
+
73
+ if self.bit_position % 8:
74
+ # Not byte aligned
75
+ result = bytearray(count)
76
+ for i in range(count):
77
+ result[i] = self.read(8)
78
+ return bytes(result)
79
+
80
+ # Byte aligned
81
+ self.byte_position = self.bit_position // 8
82
+ self.bits_cached = 0
83
+ self.cache = 0
84
+ offset = self.bit_position // 8
85
+ self.bit_position += 8 * count
86
+ return self.data[offset : offset + count]
87
+
88
+ def bits_left(self) -> int:
89
+ return (8 * len(self.data)) - self.bit_position
90
+
91
+ def skip(self, bits: int) -> None:
92
+ # Slow, but simple...
93
+ while bits:
94
+ if bits > 32:
95
+ self.read(32)
96
+ bits -= 32
97
+ else:
98
+ self.read(bits)
99
+ break
100
+
101
+
102
+ # -----------------------------------------------------------------------------
103
+ class AacAudioRtpPacket:
104
+ """AAC payload encapsulated in an RTP packet payload"""
105
+
106
+ @staticmethod
107
+ def latm_value(reader: BitReader) -> int:
108
+ bytes_for_value = reader.read(2)
109
+ value = 0
110
+ for _ in range(bytes_for_value + 1):
111
+ value = value * 256 + reader.read(8)
112
+ return value
113
+
114
+ @staticmethod
115
+ def program_config_element(reader: BitReader):
116
+ raise ValueError('program_config_element not supported')
117
+
118
+ @dataclass
119
+ class GASpecificConfig:
120
+ def __init__(
121
+ self, reader: BitReader, channel_configuration: int, audio_object_type: int
122
+ ) -> None:
123
+ # GASpecificConfig - ISO/EIC 14496-3 Table 4.1
124
+ frame_length_flag = reader.read(1)
125
+ depends_on_core_coder = reader.read(1)
126
+ if depends_on_core_coder:
127
+ self.core_coder_delay = reader.read(14)
128
+ extension_flag = reader.read(1)
129
+ if not channel_configuration:
130
+ AacAudioRtpPacket.program_config_element(reader)
131
+ if audio_object_type in (6, 20):
132
+ self.layer_nr = reader.read(3)
133
+ if extension_flag:
134
+ if audio_object_type == 22:
135
+ num_of_sub_frame = reader.read(5)
136
+ layer_length = reader.read(11)
137
+ if audio_object_type in (17, 19, 20, 23):
138
+ aac_section_data_resilience_flags = reader.read(1)
139
+ aac_scale_factor_data_resilience_flags = reader.read(1)
140
+ aac_spectral_data_resilience_flags = reader.read(1)
141
+ extension_flag_3 = reader.read(1)
142
+ if extension_flag_3 == 1:
143
+ raise ValueError('extensionFlag3 == 1 not supported')
144
+
145
+ @staticmethod
146
+ def 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
153
+
154
+ @dataclass
155
+ class AudioSpecificConfig:
156
+ audio_object_type: int
157
+ sampling_frequency_index: int
158
+ sampling_frequency: int
159
+ channel_configuration: int
160
+ sbr_present_flag: int
161
+ ps_present_flag: int
162
+ extension_audio_object_type: int
163
+ extension_sampling_frequency_index: int
164
+ extension_sampling_frequency: int
165
+ extension_channel_configuration: int
166
+
167
+ SAMPLING_FREQUENCIES = [
168
+ 96000,
169
+ 88200,
170
+ 64000,
171
+ 48000,
172
+ 44100,
173
+ 32000,
174
+ 24000,
175
+ 22050,
176
+ 16000,
177
+ 12000,
178
+ 11025,
179
+ 8000,
180
+ 7350,
181
+ ]
182
+
183
+ def __init__(self, reader: BitReader) -> None:
184
+ # AudioSpecificConfig - ISO/EIC 14496-3 Table 1.15
185
+ self.audio_object_type = AacAudioRtpPacket.audio_object_type(reader)
186
+ self.sampling_frequency_index = reader.read(4)
187
+ if self.sampling_frequency_index == 0xF:
188
+ self.sampling_frequency = reader.read(24)
189
+ else:
190
+ self.sampling_frequency = self.SAMPLING_FREQUENCIES[
191
+ self.sampling_frequency_index
192
+ ]
193
+ self.channel_configuration = reader.read(4)
194
+ self.sbr_present_flag = -1
195
+ self.ps_present_flag = -1
196
+ if self.audio_object_type in (5, 29):
197
+ self.extension_audio_object_type = 5
198
+ self.sbc_present_flag = 1
199
+ if self.audio_object_type == 29:
200
+ self.ps_present_flag = 1
201
+ self.extension_sampling_frequency_index = reader.read(4)
202
+ if self.extension_sampling_frequency_index == 0xF:
203
+ self.extension_sampling_frequency = reader.read(24)
204
+ else:
205
+ self.extension_sampling_frequency = self.SAMPLING_FREQUENCIES[
206
+ self.extension_sampling_frequency_index
207
+ ]
208
+ self.audio_object_type = AacAudioRtpPacket.audio_object_type(reader)
209
+ if self.audio_object_type == 22:
210
+ self.extension_channel_configuration = reader.read(4)
211
+ else:
212
+ self.extension_audio_object_type = 0
213
+
214
+ if self.audio_object_type in (1, 2, 3, 4, 6, 7, 17, 19, 20, 21, 22, 23):
215
+ ga_specific_config = AacAudioRtpPacket.GASpecificConfig(
216
+ reader, self.channel_configuration, self.audio_object_type
217
+ )
218
+ else:
219
+ raise ValueError(
220
+ f'audioObjectType {self.audio_object_type} not supported'
221
+ )
222
+
223
+ # if self.extension_audio_object_type != 5 and bits_to_decode >= 16:
224
+ # sync_extension_type = reader.read(11)
225
+ # if sync_extension_type == 0x2B7:
226
+ # self.extension_audio_object_type = AacAudioRtpPacket.audio_object_type(reader)
227
+ # if self.extension_audio_object_type == 5:
228
+ # self.sbr_present_flag = reader.read(1)
229
+ # if self.sbr_present_flag:
230
+ # self.extension_sampling_frequency_index = reader.read(4)
231
+ # if self.extension_sampling_frequency_index == 0xF:
232
+ # self.extension_sampling_frequency = reader.read(24)
233
+ # else:
234
+ # self.extension_sampling_frequency = self.SAMPLING_FREQUENCIES[self.extension_sampling_frequency_index]
235
+ # if bits_to_decode >= 12:
236
+ # sync_extension_type = reader.read(11)
237
+ # if sync_extension_type == 0x548:
238
+ # self.ps_present_flag = reader.read(1)
239
+ # elif self.extension_audio_object_type == 22:
240
+ # self.sbr_present_flag = reader.read(1)
241
+ # if self.sbr_present_flag:
242
+ # self.extension_sampling_frequency_index = reader.read(4)
243
+ # if self.extension_sampling_frequency_index == 0xF:
244
+ # self.extension_sampling_frequency = reader.read(24)
245
+ # else:
246
+ # self.extension_sampling_frequency = self.SAMPLING_FREQUENCIES[self.extension_sampling_frequency_index]
247
+ # self.extension_channel_configuration = reader.read(4)
248
+
249
+ @dataclass
250
+ class StreamMuxConfig:
251
+ other_data_present: int
252
+ other_data_len_bits: int
253
+ audio_specific_config: AacAudioRtpPacket.AudioSpecificConfig
254
+
255
+ def __init__(self, reader: BitReader) -> None:
256
+ # StreamMuxConfig - ISO/EIC 14496-3 Table 1.42
257
+ audio_mux_version = reader.read(1)
258
+ if audio_mux_version == 1:
259
+ audio_mux_version_a = reader.read(1)
260
+ else:
261
+ audio_mux_version_a = 0
262
+ if audio_mux_version_a != 0:
263
+ raise ValueError('audioMuxVersionA != 0 not supported')
264
+ if audio_mux_version == 1:
265
+ tara_buffer_fullness = AacAudioRtpPacket.latm_value(reader)
266
+ stream_cnt = 0
267
+ all_streams_same_time_framing = reader.read(1)
268
+ num_sub_frames = reader.read(6)
269
+ num_program = reader.read(4)
270
+ if num_program != 0:
271
+ raise ValueError('num_program != 0 not supported')
272
+ num_layer = reader.read(3)
273
+ if num_layer != 0:
274
+ raise ValueError('num_layer != 0 not supported')
275
+ if audio_mux_version == 0:
276
+ self.audio_specific_config = AacAudioRtpPacket.AudioSpecificConfig(
277
+ reader
278
+ )
279
+ else:
280
+ asc_len = AacAudioRtpPacket.latm_value(reader)
281
+ marker = reader.bit_position
282
+ self.audio_specific_config = AacAudioRtpPacket.AudioSpecificConfig(
283
+ reader
284
+ )
285
+ audio_specific_config_len = reader.bit_position - marker
286
+ if asc_len < audio_specific_config_len:
287
+ raise ValueError('audio_specific_config_len > asc_len')
288
+ asc_len -= audio_specific_config_len
289
+ reader.skip(asc_len)
290
+ frame_length_type = reader.read(3)
291
+ if frame_length_type == 0:
292
+ latm_buffer_fullness = reader.read(8)
293
+ elif frame_length_type == 1:
294
+ frame_length = reader.read(9)
295
+ else:
296
+ raise ValueError(f'frame_length_type {frame_length_type} not supported')
297
+
298
+ self.other_data_present = reader.read(1)
299
+ if self.other_data_present:
300
+ if audio_mux_version == 1:
301
+ self.other_data_len_bits = AacAudioRtpPacket.latm_value(reader)
302
+ else:
303
+ self.other_data_len_bits = 0
304
+ while True:
305
+ self.other_data_len_bits *= 256
306
+ other_data_len_esc = reader.read(1)
307
+ self.other_data_len_bits += reader.read(8)
308
+ if other_data_len_esc == 0:
309
+ break
310
+ crc_check_present = reader.read(1)
311
+ if crc_check_present:
312
+ crc_checksum = reader.read(8)
313
+
314
+ @dataclass
315
+ class AudioMuxElement:
316
+ payload: bytes
317
+ stream_mux_config: AacAudioRtpPacket.StreamMuxConfig
318
+
319
+ def __init__(self, reader: BitReader, mux_config_present: int):
320
+ if mux_config_present == 0:
321
+ raise ValueError('muxConfigPresent == 0 not supported')
322
+
323
+ # AudioMuxElement - ISO/EIC 14496-3 Table 1.41
324
+ use_same_stream_mux = reader.read(1)
325
+ if use_same_stream_mux:
326
+ raise ValueError('useSameStreamMux == 1 not supported')
327
+ self.stream_mux_config = AacAudioRtpPacket.StreamMuxConfig(reader)
328
+
329
+ # We only support:
330
+ # allStreamsSameTimeFraming == 1
331
+ # audioMuxVersionA == 0,
332
+ # numProgram == 0
333
+ # numSubFrames == 0
334
+ # numLayer == 0
335
+
336
+ mux_slot_length_bytes = 0
337
+ while True:
338
+ tmp = reader.read(8)
339
+ mux_slot_length_bytes += tmp
340
+ if tmp != 255:
341
+ break
342
+
343
+ self.payload = reader.read_bytes(mux_slot_length_bytes)
344
+
345
+ if self.stream_mux_config.other_data_present:
346
+ reader.skip(self.stream_mux_config.other_data_len_bits)
347
+
348
+ # ByteAlign
349
+ while reader.bit_position % 8:
350
+ reader.read(1)
351
+
352
+ def __init__(self, data: bytes) -> None:
353
+ # Parse the bit stream
354
+ reader = BitReader(data)
355
+ self.audio_mux_element = self.AudioMuxElement(reader, mux_config_present=1)
356
+
357
+ def to_adts(self):
358
+ # pylint: disable=line-too-long
359
+ sampling_frequency_index = (
360
+ self.audio_mux_element.stream_mux_config.audio_specific_config.sampling_frequency_index
361
+ )
362
+ channel_configuration = (
363
+ self.audio_mux_element.stream_mux_config.audio_specific_config.channel_configuration
364
+ )
365
+ frame_size = len(self.audio_mux_element.payload)
366
+ return (
367
+ bytes(
368
+ [
369
+ 0xFF,
370
+ 0xF1, # 0xF9 (MPEG2)
371
+ 0x40
372
+ | (sampling_frequency_index << 2)
373
+ | (channel_configuration >> 2),
374
+ ((channel_configuration & 0x3) << 6) | ((frame_size + 7) >> 11),
375
+ ((frame_size + 7) >> 3) & 0xFF,
376
+ (((frame_size + 7) << 5) & 0xFF) | 0x1F,
377
+ 0xFC,
378
+ ]
379
+ )
380
+ + self.audio_mux_element.payload
381
+ )
bumble/device.py CHANGED
@@ -954,12 +954,16 @@ class Device(CompositeEventEmitter):
954
954
  config.load_from_file(filename)
955
955
  return cls(config=config)
956
956
 
957
+ @classmethod
958
+ def from_config_with_hci(cls, config, hci_source, hci_sink):
959
+ host = Host(controller_source=hci_source, controller_sink=hci_sink)
960
+ return cls(config=config, host=host)
961
+
957
962
  @classmethod
958
963
  def from_config_file_with_hci(cls, filename, hci_source, hci_sink):
959
964
  config = DeviceConfiguration()
960
965
  config.load_from_file(filename)
961
- host = Host(controller_source=hci_source, controller_sink=hci_sink)
962
- return cls(config=config, host=host)
966
+ return cls.from_config_with_hci(config, hci_source, hci_sink)
963
967
 
964
968
  def __init__(
965
969
  self,
@@ -2441,7 +2445,7 @@ class Device(CompositeEventEmitter):
2441
2445
 
2442
2446
  if result.status != HCI_COMMAND_STATUS_PENDING:
2443
2447
  logger.warning(
2444
- 'HCI_Set_Connection_Encryption_Command failed: '
2448
+ 'HCI_Remote_Name_Request_Command failed: '
2445
2449
  f'{HCI_Constant.error_name(result.status)}'
2446
2450
  )
2447
2451
  raise HCI_StatusError(result)
bumble/hci.py CHANGED
@@ -62,7 +62,7 @@ def map_null_terminated_utf8_string(utf8_bytes):
62
62
  try:
63
63
  terminator = utf8_bytes.find(0)
64
64
  if terminator < 0:
65
- return utf8_bytes
65
+ terminator = len(utf8_bytes)
66
66
  return utf8_bytes[0:terminator].decode('utf8')
67
67
  except UnicodeDecodeError:
68
68
  return utf8_bytes
@@ -1795,6 +1795,16 @@ class Address:
1795
1795
  def to_bytes(self):
1796
1796
  return self.address_bytes
1797
1797
 
1798
+ def to_string(self, with_type_qualifier=True):
1799
+ '''
1800
+ String representation of the address, MSB first, with an optional type
1801
+ qualifier.
1802
+ '''
1803
+ result = ':'.join([f'{x:02X}' for x in reversed(self.address_bytes)])
1804
+ if not with_type_qualifier or not self.is_public:
1805
+ return result
1806
+ return result + '/P'
1807
+
1798
1808
  def __bytes__(self):
1799
1809
  return self.to_bytes()
1800
1810
 
@@ -1808,13 +1818,7 @@ class Address:
1808
1818
  )
1809
1819
 
1810
1820
  def __str__(self):
1811
- '''
1812
- String representation of the address, MSB first
1813
- '''
1814
- result = ':'.join([f'{x:02X}' for x in reversed(self.address_bytes)])
1815
- if not self.is_public:
1816
- return result
1817
- return result + '/P'
1821
+ return self.to_string()
1818
1822
 
1819
1823
 
1820
1824
  # Predefined address values
@@ -5373,7 +5377,7 @@ class HCI_AclDataPacket:
5373
5377
  def __str__(self):
5374
5378
  return (
5375
5379
  f'{color("ACL", "blue")}: '
5376
- f'handle=0x{self.connection_handle:04x}'
5380
+ f'handle=0x{self.connection_handle:04x}, '
5377
5381
  f'pb={self.pb_flag}, bc={self.bc_flag}, '
5378
5382
  f'data_total_length={self.data_total_length}, '
5379
5383
  f'data={self.data.hex()}'
bumble/host.py CHANGED
@@ -62,6 +62,7 @@ from .hci import (
62
62
  HCI_Read_Local_Version_Information_Command,
63
63
  HCI_Reset_Command,
64
64
  HCI_Set_Event_Mask_Command,
65
+ map_null_terminated_utf8_string,
65
66
  )
66
67
  from .core import (
67
68
  BT_BR_EDR_TRANSPORT,
@@ -887,7 +888,12 @@ class Host(AbortableEventEmitter):
887
888
  if event.status != HCI_SUCCESS:
888
889
  self.emit('remote_name_failure', event.bd_addr, event.status)
889
890
  else:
890
- self.emit('remote_name', event.bd_addr, event.remote_name)
891
+ utf8_name = event.remote_name
892
+ terminator = utf8_name.find(0)
893
+ if terminator >= 0:
894
+ utf8_name = utf8_name[0:terminator]
895
+
896
+ self.emit('remote_name', event.bd_addr, utf8_name)
891
897
 
892
898
  def on_hci_remote_host_supported_features_notification_event(self, event):
893
899
  self.emit(