bumble 0.0.204__py3-none-any.whl → 0.0.207__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/apps/auracast.py +626 -87
- bumble/apps/bench.py +225 -147
- bumble/apps/controller_info.py +23 -7
- bumble/apps/device_info.py +50 -4
- bumble/apps/lea_unicast/app.py +61 -201
- bumble/audio/__init__.py +17 -0
- bumble/audio/io.py +553 -0
- bumble/controller.py +24 -9
- bumble/core.py +4 -1
- bumble/device.py +993 -48
- bumble/gatt.py +35 -6
- bumble/gatt_client.py +14 -2
- bumble/hci.py +812 -14
- bumble/host.py +359 -63
- bumble/l2cap.py +3 -16
- bumble/profiles/aics.py +19 -38
- bumble/profiles/ascs.py +6 -18
- bumble/profiles/asha.py +5 -5
- bumble/profiles/bass.py +10 -19
- bumble/profiles/gatt_service.py +166 -0
- bumble/profiles/gmap.py +193 -0
- bumble/profiles/le_audio.py +87 -4
- bumble/profiles/pacs.py +48 -16
- bumble/profiles/tmap.py +3 -9
- bumble/profiles/{vcp.py → vcs.py} +33 -28
- bumble/profiles/vocs.py +54 -85
- bumble/sdp.py +223 -93
- bumble/smp.py +1 -1
- bumble/utils.py +2 -2
- bumble/vendor/android/hci.py +1 -1
- {bumble-0.0.204.dist-info → bumble-0.0.207.dist-info}/METADATA +12 -10
- {bumble-0.0.204.dist-info → bumble-0.0.207.dist-info}/RECORD +37 -34
- {bumble-0.0.204.dist-info → bumble-0.0.207.dist-info}/WHEEL +1 -1
- {bumble-0.0.204.dist-info → bumble-0.0.207.dist-info}/entry_points.txt +1 -0
- bumble/apps/lea_unicast/liblc3.wasm +0 -0
- {bumble-0.0.204.dist-info → bumble-0.0.207.dist-info}/LICENSE +0 -0
- {bumble-0.0.204.dist-info → bumble-0.0.207.dist-info}/top_level.txt +0 -0
bumble/apps/device_info.py
CHANGED
|
@@ -29,7 +29,9 @@ from bumble.gatt import Service
|
|
|
29
29
|
from bumble.profiles.device_information_service import DeviceInformationServiceProxy
|
|
30
30
|
from bumble.profiles.battery_service import BatteryServiceProxy
|
|
31
31
|
from bumble.profiles.gap import GenericAccessServiceProxy
|
|
32
|
+
from bumble.profiles.pacs import PublishedAudioCapabilitiesServiceProxy
|
|
32
33
|
from bumble.profiles.tmap import TelephonyAndMediaAudioServiceProxy
|
|
34
|
+
from bumble.profiles.vcs import VolumeControlServiceProxy
|
|
33
35
|
from bumble.transport import open_transport_or_link
|
|
34
36
|
|
|
35
37
|
|
|
@@ -126,14 +128,52 @@ async def show_tmas(
|
|
|
126
128
|
print(color('### Telephony And Media Audio Service', 'yellow'))
|
|
127
129
|
|
|
128
130
|
if tmas.role:
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
131
|
+
role = await tmas.role.read_value()
|
|
132
|
+
print(color(' Role:', 'green'), role)
|
|
133
|
+
|
|
134
|
+
print()
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
# -----------------------------------------------------------------------------
|
|
138
|
+
async def show_pacs(pacs: PublishedAudioCapabilitiesServiceProxy) -> None:
|
|
139
|
+
print(color('### Published Audio Capabilities Service', 'yellow'))
|
|
140
|
+
|
|
141
|
+
contexts = await pacs.available_audio_contexts.read_value()
|
|
142
|
+
print(color(' Available Audio Contexts:', 'green'), contexts)
|
|
143
|
+
|
|
144
|
+
contexts = await pacs.supported_audio_contexts.read_value()
|
|
145
|
+
print(color(' Supported Audio Contexts:', 'green'), contexts)
|
|
146
|
+
|
|
147
|
+
if pacs.sink_pac:
|
|
148
|
+
pac = await pacs.sink_pac.read_value()
|
|
149
|
+
print(color(' Sink PAC: ', 'green'), pac)
|
|
150
|
+
|
|
151
|
+
if pacs.sink_audio_locations:
|
|
152
|
+
audio_locations = await pacs.sink_audio_locations.read_value()
|
|
153
|
+
print(color(' Sink Audio Locations: ', 'green'), audio_locations)
|
|
154
|
+
|
|
155
|
+
if pacs.source_pac:
|
|
156
|
+
pac = await pacs.source_pac.read_value()
|
|
157
|
+
print(color(' Source PAC: ', 'green'), pac)
|
|
158
|
+
|
|
159
|
+
if pacs.source_audio_locations:
|
|
160
|
+
audio_locations = await pacs.source_audio_locations.read_value()
|
|
161
|
+
print(color(' Source Audio Locations: ', 'green'), audio_locations)
|
|
133
162
|
|
|
134
163
|
print()
|
|
135
164
|
|
|
136
165
|
|
|
166
|
+
# -----------------------------------------------------------------------------
|
|
167
|
+
async def show_vcs(vcs: VolumeControlServiceProxy) -> None:
|
|
168
|
+
print(color('### Volume Control Service', 'yellow'))
|
|
169
|
+
|
|
170
|
+
volume_state = await vcs.volume_state.read_value()
|
|
171
|
+
print(color(' Volume State:', 'green'), volume_state)
|
|
172
|
+
|
|
173
|
+
volume_flags = await vcs.volume_flags.read_value()
|
|
174
|
+
print(color(' Volume Flags:', 'green'), volume_flags)
|
|
175
|
+
|
|
176
|
+
|
|
137
177
|
# -----------------------------------------------------------------------------
|
|
138
178
|
async def show_device_info(peer, done: Optional[asyncio.Future]) -> None:
|
|
139
179
|
try:
|
|
@@ -161,6 +201,12 @@ async def show_device_info(peer, done: Optional[asyncio.Future]) -> None:
|
|
|
161
201
|
if tmas := peer.create_service_proxy(TelephonyAndMediaAudioServiceProxy):
|
|
162
202
|
await try_show(show_tmas, tmas)
|
|
163
203
|
|
|
204
|
+
if pacs := peer.create_service_proxy(PublishedAudioCapabilitiesServiceProxy):
|
|
205
|
+
await try_show(show_pacs, pacs)
|
|
206
|
+
|
|
207
|
+
if vcs := peer.create_service_proxy(VolumeControlServiceProxy):
|
|
208
|
+
await try_show(show_vcs, vcs)
|
|
209
|
+
|
|
164
210
|
if done is not None:
|
|
165
211
|
done.set_result(None)
|
|
166
212
|
except asyncio.CancelledError:
|
bumble/apps/lea_unicast/app.py
CHANGED
|
@@ -16,23 +16,22 @@
|
|
|
16
16
|
# Imports
|
|
17
17
|
# -----------------------------------------------------------------------------
|
|
18
18
|
from __future__ import annotations
|
|
19
|
+
|
|
19
20
|
import asyncio
|
|
20
21
|
import datetime
|
|
21
|
-
import enum
|
|
22
22
|
import functools
|
|
23
23
|
from importlib import resources
|
|
24
24
|
import json
|
|
25
25
|
import os
|
|
26
26
|
import logging
|
|
27
27
|
import pathlib
|
|
28
|
-
from typing import Optional, List, cast
|
|
29
28
|
import weakref
|
|
30
|
-
import
|
|
29
|
+
import wave
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
import
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
try:
|
|
32
|
+
import lc3 # type: ignore # pylint: disable=E0401
|
|
33
|
+
except ImportError as e:
|
|
34
|
+
raise ImportError("Try `python -m pip install \".[lc3]\"`.") from e
|
|
36
35
|
|
|
37
36
|
import click
|
|
38
37
|
import aiohttp.web
|
|
@@ -40,11 +39,12 @@ import aiohttp.web
|
|
|
40
39
|
import bumble
|
|
41
40
|
from bumble.core import AdvertisingData
|
|
42
41
|
from bumble.colors import color
|
|
43
|
-
from bumble.device import Device, DeviceConfiguration, AdvertisingParameters
|
|
42
|
+
from bumble.device import Device, DeviceConfiguration, AdvertisingParameters, CisLink
|
|
44
43
|
from bumble.transport import open_transport
|
|
45
44
|
from bumble.profiles import ascs, bap, pacs
|
|
46
45
|
from bumble.hci import Address, CodecID, CodingFormat, HCI_IsoDataPacket
|
|
47
46
|
|
|
47
|
+
|
|
48
48
|
# -----------------------------------------------------------------------------
|
|
49
49
|
# Logging
|
|
50
50
|
# -----------------------------------------------------------------------------
|
|
@@ -54,6 +54,7 @@ logger = logging.getLogger(__name__)
|
|
|
54
54
|
# Constants
|
|
55
55
|
# -----------------------------------------------------------------------------
|
|
56
56
|
DEFAULT_UI_PORT = 7654
|
|
57
|
+
DEFAULT_PCM_BYTES_PER_SAMPLE = 2
|
|
57
58
|
|
|
58
59
|
|
|
59
60
|
def _sink_pac_record() -> pacs.PacRecord:
|
|
@@ -100,153 +101,8 @@ def _source_pac_record() -> pacs.PacRecord:
|
|
|
100
101
|
)
|
|
101
102
|
|
|
102
103
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
# -----------------------------------------------------------------------------
|
|
106
|
-
store = wasmtime.loader.store
|
|
107
|
-
_memory = cast(wasmtime.Memory, liblc3.memory)
|
|
108
|
-
STACK_POINTER = _memory.data_len(store)
|
|
109
|
-
_memory.grow(store, 1)
|
|
110
|
-
# Mapping wasmtime memory to linear address
|
|
111
|
-
memory = (ctypes.c_ubyte * _memory.data_len(store)).from_address(
|
|
112
|
-
ctypes.addressof(_memory.data_ptr(store).contents) # type: ignore
|
|
113
|
-
)
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
class Liblc3PcmFormat(enum.IntEnum):
|
|
117
|
-
S16 = 0
|
|
118
|
-
S24 = 1
|
|
119
|
-
S24_3LE = 2
|
|
120
|
-
FLOAT = 3
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
MAX_DECODER_SIZE = liblc3.lc3_decoder_size(10000, 48000)
|
|
124
|
-
MAX_ENCODER_SIZE = liblc3.lc3_encoder_size(10000, 48000)
|
|
125
|
-
|
|
126
|
-
DECODER_STACK_POINTER = STACK_POINTER
|
|
127
|
-
ENCODER_STACK_POINTER = DECODER_STACK_POINTER + MAX_DECODER_SIZE * 2
|
|
128
|
-
DECODE_BUFFER_STACK_POINTER = ENCODER_STACK_POINTER + MAX_ENCODER_SIZE * 2
|
|
129
|
-
ENCODE_BUFFER_STACK_POINTER = DECODE_BUFFER_STACK_POINTER + 8192
|
|
130
|
-
DEFAULT_PCM_SAMPLE_RATE = 48000
|
|
131
|
-
DEFAULT_PCM_FORMAT = Liblc3PcmFormat.S16
|
|
132
|
-
DEFAULT_PCM_BYTES_PER_SAMPLE = 2
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
encoders: List[int] = []
|
|
136
|
-
decoders: List[int] = []
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
def setup_encoders(
|
|
140
|
-
sample_rate_hz: int, frame_duration_us: int, num_channels: int
|
|
141
|
-
) -> None:
|
|
142
|
-
logger.info(
|
|
143
|
-
f"setup_encoders {sample_rate_hz}Hz {frame_duration_us}us {num_channels}channels"
|
|
144
|
-
)
|
|
145
|
-
encoders[:num_channels] = [
|
|
146
|
-
liblc3.lc3_setup_encoder(
|
|
147
|
-
frame_duration_us,
|
|
148
|
-
sample_rate_hz,
|
|
149
|
-
DEFAULT_PCM_SAMPLE_RATE, # Input sample rate
|
|
150
|
-
ENCODER_STACK_POINTER + MAX_ENCODER_SIZE * i,
|
|
151
|
-
)
|
|
152
|
-
for i in range(num_channels)
|
|
153
|
-
]
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
def setup_decoders(
|
|
157
|
-
sample_rate_hz: int, frame_duration_us: int, num_channels: int
|
|
158
|
-
) -> None:
|
|
159
|
-
logger.info(
|
|
160
|
-
f"setup_decoders {sample_rate_hz}Hz {frame_duration_us}us {num_channels}channels"
|
|
161
|
-
)
|
|
162
|
-
decoders[:num_channels] = [
|
|
163
|
-
liblc3.lc3_setup_decoder(
|
|
164
|
-
frame_duration_us,
|
|
165
|
-
sample_rate_hz,
|
|
166
|
-
DEFAULT_PCM_SAMPLE_RATE, # Output sample rate
|
|
167
|
-
DECODER_STACK_POINTER + MAX_DECODER_SIZE * i,
|
|
168
|
-
)
|
|
169
|
-
for i in range(num_channels)
|
|
170
|
-
]
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
def decode(
|
|
174
|
-
frame_duration_us: int,
|
|
175
|
-
num_channels: int,
|
|
176
|
-
input_bytes: bytes,
|
|
177
|
-
) -> bytes:
|
|
178
|
-
if not input_bytes:
|
|
179
|
-
return b''
|
|
180
|
-
|
|
181
|
-
input_buffer_offset = DECODE_BUFFER_STACK_POINTER
|
|
182
|
-
input_buffer_size = len(input_bytes)
|
|
183
|
-
input_bytes_per_frame = input_buffer_size // num_channels
|
|
184
|
-
|
|
185
|
-
# Copy into wasm
|
|
186
|
-
memory[input_buffer_offset : input_buffer_offset + input_buffer_size] = input_bytes # type: ignore
|
|
187
|
-
|
|
188
|
-
output_buffer_offset = input_buffer_offset + input_buffer_size
|
|
189
|
-
output_buffer_size = (
|
|
190
|
-
liblc3.lc3_frame_samples(frame_duration_us, DEFAULT_PCM_SAMPLE_RATE)
|
|
191
|
-
* DEFAULT_PCM_BYTES_PER_SAMPLE
|
|
192
|
-
* num_channels
|
|
193
|
-
)
|
|
194
|
-
|
|
195
|
-
for i in range(num_channels):
|
|
196
|
-
res = liblc3.lc3_decode(
|
|
197
|
-
decoders[i],
|
|
198
|
-
input_buffer_offset + input_bytes_per_frame * i,
|
|
199
|
-
input_bytes_per_frame,
|
|
200
|
-
DEFAULT_PCM_FORMAT,
|
|
201
|
-
output_buffer_offset + i * DEFAULT_PCM_BYTES_PER_SAMPLE,
|
|
202
|
-
num_channels, # Stride
|
|
203
|
-
)
|
|
204
|
-
|
|
205
|
-
if res != 0:
|
|
206
|
-
logging.error(f"Parsing failed, res={res}")
|
|
207
|
-
|
|
208
|
-
# Extract decoded data from the output buffer
|
|
209
|
-
return bytes(
|
|
210
|
-
memory[output_buffer_offset : output_buffer_offset + output_buffer_size]
|
|
211
|
-
)
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
def encode(
|
|
215
|
-
sdu_length: int,
|
|
216
|
-
num_channels: int,
|
|
217
|
-
stride: int,
|
|
218
|
-
input_bytes: bytes,
|
|
219
|
-
) -> bytes:
|
|
220
|
-
if not input_bytes:
|
|
221
|
-
return b''
|
|
222
|
-
|
|
223
|
-
input_buffer_offset = ENCODE_BUFFER_STACK_POINTER
|
|
224
|
-
input_buffer_size = len(input_bytes)
|
|
225
|
-
|
|
226
|
-
# Copy into wasm
|
|
227
|
-
memory[input_buffer_offset : input_buffer_offset + input_buffer_size] = input_bytes # type: ignore
|
|
228
|
-
|
|
229
|
-
output_buffer_offset = input_buffer_offset + input_buffer_size
|
|
230
|
-
output_buffer_size = sdu_length
|
|
231
|
-
output_frame_size = output_buffer_size // num_channels
|
|
232
|
-
|
|
233
|
-
for i in range(num_channels):
|
|
234
|
-
res = liblc3.lc3_encode(
|
|
235
|
-
encoders[i],
|
|
236
|
-
DEFAULT_PCM_FORMAT,
|
|
237
|
-
input_buffer_offset + DEFAULT_PCM_BYTES_PER_SAMPLE * i,
|
|
238
|
-
stride,
|
|
239
|
-
output_frame_size,
|
|
240
|
-
output_buffer_offset + output_frame_size * i,
|
|
241
|
-
)
|
|
242
|
-
|
|
243
|
-
if res != 0:
|
|
244
|
-
logging.error(f"Parsing failed, res={res}")
|
|
245
|
-
|
|
246
|
-
# Extract decoded data from the output buffer
|
|
247
|
-
return bytes(
|
|
248
|
-
memory[output_buffer_offset : output_buffer_offset + output_buffer_size]
|
|
249
|
-
)
|
|
104
|
+
decoder: lc3.Decoder | None = None
|
|
105
|
+
encoding_config: bap.CodecSpecificConfiguration | None = None
|
|
250
106
|
|
|
251
107
|
|
|
252
108
|
async def lc3_source_task(
|
|
@@ -254,44 +110,49 @@ async def lc3_source_task(
|
|
|
254
110
|
sdu_length: int,
|
|
255
111
|
frame_duration_us: int,
|
|
256
112
|
device: Device,
|
|
257
|
-
|
|
113
|
+
cis_link: CisLink,
|
|
258
114
|
) -> None:
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
assert bits_per_sample == DEFAULT_PCM_BYTES_PER_SAMPLE * 8
|
|
115
|
+
logger.info(
|
|
116
|
+
"lc3_source_task filename=%s, sdu_length=%d, frame_duration=%.1f",
|
|
117
|
+
filename,
|
|
118
|
+
sdu_length,
|
|
119
|
+
frame_duration_us / 1000,
|
|
120
|
+
)
|
|
121
|
+
with wave.open(filename, 'rb') as wav:
|
|
122
|
+
bits_per_sample = wav.getsampwidth() * 8
|
|
268
123
|
|
|
269
|
-
|
|
270
|
-
liblc3.lc3_frame_samples(frame_duration_us, DEFAULT_PCM_SAMPLE_RATE)
|
|
271
|
-
* DEFAULT_PCM_BYTES_PER_SAMPLE
|
|
272
|
-
)
|
|
273
|
-
packet_sequence_number = 0
|
|
124
|
+
encoder: lc3.Encoder | None = None
|
|
274
125
|
|
|
275
126
|
while True:
|
|
276
127
|
next_round = datetime.datetime.now() + datetime.timedelta(
|
|
277
128
|
microseconds=frame_duration_us
|
|
278
129
|
)
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
130
|
+
if not encoder:
|
|
131
|
+
if (
|
|
132
|
+
encoding_config
|
|
133
|
+
and (frame_duration := encoding_config.frame_duration)
|
|
134
|
+
and (sampling_frequency := encoding_config.sampling_frequency)
|
|
135
|
+
and (
|
|
136
|
+
audio_channel_allocation := encoding_config.audio_channel_allocation
|
|
137
|
+
)
|
|
138
|
+
):
|
|
139
|
+
logger.info("Use %s", encoding_config)
|
|
140
|
+
encoder = lc3.Encoder(
|
|
141
|
+
frame_duration_us=frame_duration.us,
|
|
142
|
+
sample_rate_hz=sampling_frequency.hz,
|
|
143
|
+
num_channels=audio_channel_allocation.channel_count,
|
|
144
|
+
input_sample_rate_hz=wav.getframerate(),
|
|
145
|
+
)
|
|
146
|
+
else:
|
|
147
|
+
sdu = encoder.encode(
|
|
148
|
+
pcm=wav.readframes(encoder.get_frame_samples()),
|
|
149
|
+
num_bytes=sdu_length,
|
|
150
|
+
bit_depth=bits_per_sample,
|
|
151
|
+
)
|
|
152
|
+
cis_link.write(sdu)
|
|
153
|
+
|
|
293
154
|
sleep_time = next_round - datetime.datetime.now()
|
|
294
|
-
await asyncio.sleep(sleep_time.total_seconds())
|
|
155
|
+
await asyncio.sleep(sleep_time.total_seconds() * 0.9)
|
|
295
156
|
|
|
296
157
|
|
|
297
158
|
# -----------------------------------------------------------------------------
|
|
@@ -410,7 +271,7 @@ class Speaker:
|
|
|
410
271
|
|
|
411
272
|
def __init__(
|
|
412
273
|
self,
|
|
413
|
-
device_config_path:
|
|
274
|
+
device_config_path: str | None,
|
|
414
275
|
ui_port: int,
|
|
415
276
|
transport: str,
|
|
416
277
|
lc3_input_file_path: str,
|
|
@@ -437,6 +298,7 @@ class Speaker:
|
|
|
437
298
|
advertising_interval_min=25,
|
|
438
299
|
advertising_interval_max=25,
|
|
439
300
|
address=Address('F1:F2:F3:F4:F5:F6'),
|
|
301
|
+
identity_address_type=Address.RANDOM_DEVICE_ADDRESS,
|
|
440
302
|
)
|
|
441
303
|
|
|
442
304
|
device_config.le_enabled = True
|
|
@@ -490,12 +352,12 @@ class Speaker:
|
|
|
490
352
|
not isinstance(codec_config, bap.CodecSpecificConfiguration)
|
|
491
353
|
or codec_config.frame_duration is None
|
|
492
354
|
or codec_config.audio_channel_allocation is None
|
|
355
|
+
or decoder is None
|
|
356
|
+
or not pdu.iso_sdu_fragment
|
|
493
357
|
):
|
|
494
358
|
return
|
|
495
|
-
pcm = decode(
|
|
496
|
-
|
|
497
|
-
codec_config.audio_channel_allocation.channel_count,
|
|
498
|
-
pdu.iso_sdu_fragment,
|
|
359
|
+
pcm = decoder.decode(
|
|
360
|
+
pdu.iso_sdu_fragment, bit_depth=DEFAULT_PCM_BYTES_PER_SAMPLE * 8
|
|
499
361
|
)
|
|
500
362
|
self.device.abort_on('disconnection', self.ui_server.send_audio(pcm))
|
|
501
363
|
|
|
@@ -521,7 +383,7 @@ class Speaker:
|
|
|
521
383
|
),
|
|
522
384
|
frame_duration_us=codec_config.frame_duration.us,
|
|
523
385
|
device=self.device,
|
|
524
|
-
|
|
386
|
+
cis_link=ase.cis_link,
|
|
525
387
|
),
|
|
526
388
|
)
|
|
527
389
|
else:
|
|
@@ -537,16 +399,14 @@ class Speaker:
|
|
|
537
399
|
):
|
|
538
400
|
return
|
|
539
401
|
if ase.role == ascs.AudioRole.SOURCE:
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
codec_config.frame_duration.us,
|
|
543
|
-
codec_config.audio_channel_allocation.channel_count,
|
|
544
|
-
)
|
|
402
|
+
global encoding_config
|
|
403
|
+
encoding_config = codec_config
|
|
545
404
|
else:
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
codec_config.frame_duration.us,
|
|
549
|
-
codec_config.
|
|
405
|
+
global decoder
|
|
406
|
+
decoder = lc3.Decoder(
|
|
407
|
+
frame_duration_us=codec_config.frame_duration.us,
|
|
408
|
+
sample_rate_hz=codec_config.sampling_frequency.hz,
|
|
409
|
+
num_channels=codec_config.audio_channel_allocation.channel_count,
|
|
550
410
|
)
|
|
551
411
|
|
|
552
412
|
for ase in ascs_service.ase_state_machines.values():
|
|
@@ -585,7 +445,7 @@ def speaker(ui_port: int, device_config: str, transport: str, lc3_file: str) ->
|
|
|
585
445
|
|
|
586
446
|
# -----------------------------------------------------------------------------
|
|
587
447
|
def main():
|
|
588
|
-
logging.basicConfig(level=os.environ.get('BUMBLE_LOGLEVEL', '
|
|
448
|
+
logging.basicConfig(level=os.environ.get('BUMBLE_LOGLEVEL', 'INFO').upper())
|
|
589
449
|
speaker()
|
|
590
450
|
|
|
591
451
|
|
bumble/audio/__init__.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Copyright 2025 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
|
+
# -----------------------------------------------------------------------------
|