bumble 0.0.154__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/pair.py +14 -14
- bumble/apps/scan.py +5 -6
- 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/apps/unbond.py +40 -22
- bumble/avdtp.py +50 -31
- bumble/codecs.py +381 -0
- bumble/device.py +17 -4
- bumble/hci.py +13 -9
- bumble/hfp.py +14 -8
- bumble/host.py +7 -1
- bumble/keys.py +72 -46
- bumble/pandora/host.py +2 -1
- bumble/pandora/security.py +4 -7
- bumble/rfcomm.py +110 -67
- bumble/smp.py +1 -1
- {bumble-0.0.154.dist-info → bumble-0.0.156.dist-info}/METADATA +5 -4
- {bumble-0.0.154.dist-info → bumble-0.0.156.dist-info}/RECORD +28 -21
- {bumble-0.0.154.dist-info → bumble-0.0.156.dist-info}/entry_points.txt +1 -0
- {bumble-0.0.154.dist-info → bumble-0.0.156.dist-info}/LICENSE +0 -0
- {bumble-0.0.154.dist-info → bumble-0.0.156.dist-info}/WHEEL +0 -0
- {bumble-0.0.154.dist-info → bumble-0.0.156.dist-info}/top_level.txt +0 -0
bumble/apps/unbond.py
CHANGED
|
@@ -22,40 +22,58 @@ import click
|
|
|
22
22
|
|
|
23
23
|
from bumble.device import Device
|
|
24
24
|
from bumble.keys import JsonKeyStore
|
|
25
|
-
|
|
25
|
+
from bumble.transport import open_transport
|
|
26
26
|
|
|
27
27
|
# -----------------------------------------------------------------------------
|
|
28
|
-
async def
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
async def unbond_with_keystore(keystore, address):
|
|
29
|
+
if address is None:
|
|
30
|
+
return await keystore.print()
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
await keystore.delete(address)
|
|
34
|
+
except KeyError:
|
|
35
|
+
print('!!! pairing not found')
|
|
31
36
|
|
|
32
|
-
|
|
37
|
+
|
|
38
|
+
# -----------------------------------------------------------------------------
|
|
39
|
+
async def unbond(keystore_file, device_config, hci_transport, address):
|
|
40
|
+
# With a keystore file, we can instantiate the keystore directly
|
|
33
41
|
if keystore_file:
|
|
34
|
-
|
|
35
|
-
else:
|
|
36
|
-
keystore = device.keystore
|
|
42
|
+
return await unbond_with_keystore(JsonKeyStore(None, keystore_file), address)
|
|
37
43
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
44
|
+
# Without a keystore file, we need to obtain the keystore from the device
|
|
45
|
+
async with await open_transport(hci_transport) as (hci_source, hci_sink):
|
|
46
|
+
# Create a device to manage the host
|
|
47
|
+
device = Device.from_config_file_with_hci(device_config, hci_source, hci_sink)
|
|
41
48
|
|
|
42
|
-
|
|
43
|
-
await
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
await keystore.delete(address)
|
|
47
|
-
except KeyError:
|
|
48
|
-
print('!!! pairing not found')
|
|
49
|
+
# Power-on the device to ensure we have a key store
|
|
50
|
+
await device.power_on()
|
|
51
|
+
|
|
52
|
+
return await unbond_with_keystore(device.keystore, address)
|
|
49
53
|
|
|
50
54
|
|
|
51
55
|
# -----------------------------------------------------------------------------
|
|
52
56
|
@click.command()
|
|
53
|
-
@click.option('--keystore-file', help='File in which
|
|
54
|
-
@click.
|
|
57
|
+
@click.option('--keystore-file', help='File in which the pairing keys are stored')
|
|
58
|
+
@click.option('--hci-transport', help='HCI transport for the controller')
|
|
59
|
+
@click.argument('device-config', required=False)
|
|
55
60
|
@click.argument('address', required=False)
|
|
56
|
-
def main(keystore_file, device_config, address):
|
|
61
|
+
def main(keystore_file, hci_transport, device_config, address):
|
|
62
|
+
"""
|
|
63
|
+
Remove pairing keys for a device, given its address.
|
|
64
|
+
|
|
65
|
+
If no keystore file is specified, the --hci-transport option must be used to
|
|
66
|
+
connect to a controller, so that the keystore for that controller can be
|
|
67
|
+
instantiated.
|
|
68
|
+
If no address is passed, the existing pairing keys for all addresses are printed.
|
|
69
|
+
"""
|
|
57
70
|
logging.basicConfig(level=os.environ.get('BUMBLE_LOGLEVEL', 'INFO').upper())
|
|
58
|
-
|
|
71
|
+
|
|
72
|
+
if not keystore_file and not hci_transport:
|
|
73
|
+
print('either --keystore-file or --hci-transport must be specified.')
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
asyncio.run(unbond(keystore_file, device_config, hci_transport, address))
|
|
59
77
|
|
|
60
78
|
|
|
61
79
|
# -----------------------------------------------------------------------------
|
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
|
+
)
|