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/_version.py +2 -2
- bumble/a2dp.py +1 -0
- bumble/apps/speaker/__init__.py +0 -0
- bumble/apps/speaker/logo.svg +42 -0
- bumble/apps/speaker/speaker.css +76 -0
- bumble/apps/speaker/speaker.html +34 -0
- bumble/apps/speaker/speaker.js +315 -0
- bumble/apps/speaker/speaker.py +747 -0
- bumble/avdtp.py +50 -31
- bumble/codecs.py +381 -0
- bumble/device.py +7 -3
- bumble/hci.py +13 -9
- bumble/host.py +7 -1
- {bumble-0.0.155.dist-info → bumble-0.0.156.dist-info}/METADATA +5 -4
- {bumble-0.0.155.dist-info → bumble-0.0.156.dist-info}/RECORD +19 -12
- {bumble-0.0.155.dist-info → bumble-0.0.156.dist-info}/entry_points.txt +1 -0
- {bumble-0.0.155.dist-info → bumble-0.0.156.dist-info}/LICENSE +0 -0
- {bumble-0.0.155.dist-info → bumble-0.0.156.dist-info}/WHEEL +0 -0
- {bumble-0.0.155.dist-info → bumble-0.0.156.dist-info}/top_level.txt +0 -0
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2023
|
+
self.emit('open')
|
|
1996
2024
|
|
|
1997
2025
|
def on_start_command(self):
|
|
1998
|
-
|
|
2026
|
+
self.emit('start')
|
|
1999
2027
|
|
|
2000
2028
|
def on_suspend_command(self):
|
|
2001
|
-
|
|
2029
|
+
self.emit('suspend')
|
|
2002
2030
|
|
|
2003
2031
|
def on_close_command(self):
|
|
2004
|
-
|
|
2032
|
+
self.emit('close')
|
|
2005
2033
|
|
|
2006
2034
|
def on_abort_command(self):
|
|
2007
|
-
|
|
2035
|
+
self.emit('abort')
|
|
2008
2036
|
|
|
2009
2037
|
def on_rtp_channel_open(self):
|
|
2010
|
-
|
|
2038
|
+
self.emit('rtp_channel_open')
|
|
2011
2039
|
|
|
2012
2040
|
def on_rtp_channel_close(self):
|
|
2013
|
-
|
|
2041
|
+
self.emit('rtp_channel_close')
|
|
2014
2042
|
|
|
2015
2043
|
|
|
2016
2044
|
# -----------------------------------------------------------------------------
|
|
2017
|
-
class LocalSource(LocalStreamEndPoint
|
|
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
|
-
|
|
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'
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
'
|
|
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
|
-
|
|
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
|
-
|
|
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(
|