From 1b66f03dbe5f78b2c2ccbe1cef9e3b95f016c58e Mon Sep 17 00:00:00 2001 From: Josh Wu Date: Wed, 13 Dec 2023 16:25:19 +0800 Subject: [PATCH] ASCS: Add Source ASE operations --- .vscode/settings.json | 4 + apps/lea_unicast/app.py | 577 +++++++++++++++++++++++++++++++++ apps/lea_unicast/index.html | 68 ++++ apps/lea_unicast/liblc3.wasm | Bin 0 -> 158603 bytes bumble/device.py | 115 ++++--- bumble/hci.py | 39 +-- bumble/host.py | 10 +- bumble/profiles/bap.py | 63 +++- examples/run_hfp_gateway.py | 6 +- examples/run_unicast_server.py | 155 +++++---- examples/run_vcp_renderer.py | 2 +- setup.cfg | 1 + tests/bap_test.py | 8 +- tests/device_test.py | 89 +++++ 14 files changed, 980 insertions(+), 157 deletions(-) create mode 100644 apps/lea_unicast/app.py create mode 100644 apps/lea_unicast/index.html create mode 100755 apps/lea_unicast/liblc3.wasm diff --git a/.vscode/settings.json b/.vscode/settings.json index b535ada8..777c47b4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,7 @@ { "cSpell.words": [ "Abortable", + "aiohttp", "altsetting", "ansiblue", "ansicyan", @@ -9,6 +10,7 @@ "ansired", "ansiyellow", "appendleft", + "ascs", "ASHA", "asyncio", "ATRAC", @@ -43,6 +45,7 @@ "keyup", "levelname", "libc", + "liblc", "libusb", "MITM", "MSBC", @@ -78,6 +81,7 @@ "unmuted", "usbmodem", "vhci", + "wasmtime", "websockets", "xcursor", "ycursor" diff --git a/apps/lea_unicast/app.py b/apps/lea_unicast/app.py new file mode 100644 index 00000000..ae3b4422 --- /dev/null +++ b/apps/lea_unicast/app.py @@ -0,0 +1,577 @@ +# Copyright 2021-2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ----------------------------------------------------------------------------- +# Imports +# ----------------------------------------------------------------------------- +from __future__ import annotations +import asyncio +import datetime +import enum +import functools +from importlib import resources +import json +import os +import logging +import pathlib +from typing import Optional, List, cast +import weakref +import struct + +import ctypes +import wasmtime +import wasmtime.loader +import liblc3 # type: ignore +import logging + +import click +import aiohttp.web + +import bumble +from bumble.core import AdvertisingData +from bumble.colors import color +from bumble.device import Device, DeviceConfiguration, AdvertisingParameters +from bumble.transport import open_transport +from bumble.profiles import bap +from bumble.hci import Address, CodecID, CodingFormat, HCI_IsoDataPacket + +# ----------------------------------------------------------------------------- +# Logging +# ----------------------------------------------------------------------------- +logger = logging.getLogger(__name__) + +# ----------------------------------------------------------------------------- +# Constants +# ----------------------------------------------------------------------------- +DEFAULT_UI_PORT = 7654 + + +def _sink_pac_record() -> bap.PacRecord: + return bap.PacRecord( + coding_format=CodingFormat(CodecID.LC3), + codec_specific_capabilities=bap.CodecSpecificCapabilities( + supported_sampling_frequencies=( + bap.SupportedSamplingFrequency.FREQ_8000 + | bap.SupportedSamplingFrequency.FREQ_16000 + | bap.SupportedSamplingFrequency.FREQ_24000 + | bap.SupportedSamplingFrequency.FREQ_32000 + | bap.SupportedSamplingFrequency.FREQ_48000 + ), + supported_frame_durations=( + bap.SupportedFrameDuration.DURATION_10000_US_SUPPORTED + ), + supported_audio_channel_count=[1, 2], + min_octets_per_codec_frame=26, + max_octets_per_codec_frame=240, + supported_max_codec_frames_per_sdu=2, + ), + ) + + +def _source_pac_record() -> bap.PacRecord: + return bap.PacRecord( + coding_format=CodingFormat(CodecID.LC3), + codec_specific_capabilities=bap.CodecSpecificCapabilities( + supported_sampling_frequencies=( + bap.SupportedSamplingFrequency.FREQ_8000 + | bap.SupportedSamplingFrequency.FREQ_16000 + | bap.SupportedSamplingFrequency.FREQ_24000 + | bap.SupportedSamplingFrequency.FREQ_32000 + | bap.SupportedSamplingFrequency.FREQ_48000 + ), + supported_frame_durations=( + bap.SupportedFrameDuration.DURATION_10000_US_SUPPORTED + ), + supported_audio_channel_count=[1], + min_octets_per_codec_frame=30, + max_octets_per_codec_frame=100, + supported_max_codec_frames_per_sdu=1, + ), + ) + + +# ----------------------------------------------------------------------------- +# WASM - liblc3 +# ----------------------------------------------------------------------------- +store = wasmtime.loader.store +_memory = cast(wasmtime.Memory, liblc3.memory) +STACK_POINTER = _memory.data_len(store) +_memory.grow(store, 1) +# Mapping wasmtime memory to linear address +memory = (ctypes.c_ubyte * _memory.data_len(store)).from_address( + ctypes.addressof(_memory.data_ptr(store).contents) # type: ignore +) + + +class Liblc3PcmFormat(enum.IntEnum): + S16 = 0 + S24 = 1 + S24_3LE = 2 + FLOAT = 3 + + +MAX_DECODER_SIZE = liblc3.lc3_decoder_size(10000, 48000) +MAX_ENCODER_SIZE = liblc3.lc3_encoder_size(10000, 48000) + +DECODER_STACK_POINTER = STACK_POINTER +ENCODER_STACK_POINTER = DECODER_STACK_POINTER + MAX_DECODER_SIZE * 2 +DECODE_BUFFER_STACK_POINTER = ENCODER_STACK_POINTER + MAX_ENCODER_SIZE * 2 +ENCODE_BUFFER_STACK_POINTER = DECODE_BUFFER_STACK_POINTER + 8192 +DEFAULT_PCM_SAMPLE_RATE = 48000 +DEFAULT_PCM_FORMAT = Liblc3PcmFormat.S16 +DEFAULT_PCM_BYTES_PER_SAMPLE = 2 + + +encoders: List[int] = [] +decoders: List[int] = [] + + +def setup_encoders( + sample_rate_hz: int, frame_duration_us: int, num_channels: int +) -> None: + logger.info( + f"setup_encoders {sample_rate_hz}Hz {frame_duration_us}us {num_channels}channels" + ) + encoders[:num_channels] = [ + liblc3.lc3_setup_encoder( + frame_duration_us, + sample_rate_hz, + DEFAULT_PCM_SAMPLE_RATE, # Input sample rate + ENCODER_STACK_POINTER + MAX_ENCODER_SIZE * i, + ) + for i in range(num_channels) + ] + + +def setup_decoders( + sample_rate_hz: int, frame_duration_us: int, num_channels: int +) -> None: + logger.info( + f"setup_decoders {sample_rate_hz}Hz {frame_duration_us}us {num_channels}channels" + ) + decoders[:num_channels] = [ + liblc3.lc3_setup_decoder( + frame_duration_us, + sample_rate_hz, + DEFAULT_PCM_SAMPLE_RATE, # Output sample rate + DECODER_STACK_POINTER + MAX_DECODER_SIZE * i, + ) + for i in range(num_channels) + ] + + +def decode( + frame_duration_us: int, + num_channels: int, + input_bytes: bytes, +) -> bytes: + if not input_bytes: + return b'' + + input_buffer_offset = DECODE_BUFFER_STACK_POINTER + input_buffer_size = len(input_bytes) + input_bytes_per_frame = input_buffer_size // num_channels + + # Copy into wasm + memory[input_buffer_offset : input_buffer_offset + input_buffer_size] = input_bytes # type: ignore + + output_buffer_offset = input_buffer_offset + input_buffer_size + output_buffer_size = ( + liblc3.lc3_frame_samples(frame_duration_us, DEFAULT_PCM_SAMPLE_RATE) + * DEFAULT_PCM_BYTES_PER_SAMPLE + * num_channels + ) + + for i in range(num_channels): + res = liblc3.lc3_decode( + decoders[i], + input_buffer_offset + input_bytes_per_frame * i, + input_bytes_per_frame, + DEFAULT_PCM_FORMAT, + output_buffer_offset + i * DEFAULT_PCM_BYTES_PER_SAMPLE, + num_channels, # Stride + ) + + if res != 0: + logging.error(f"Parsing failed, res={res}") + + # Extract decoded data from the output buffer + return bytes( + memory[output_buffer_offset : output_buffer_offset + output_buffer_size] + ) + + +def encode( + sdu_length: int, + num_channels: int, + stride: int, + input_bytes: bytes, +) -> bytes: + if not input_bytes: + return b'' + + input_buffer_offset = ENCODE_BUFFER_STACK_POINTER + input_buffer_size = len(input_bytes) + + # Copy into wasm + memory[input_buffer_offset : input_buffer_offset + input_buffer_size] = input_bytes # type: ignore + + output_buffer_offset = input_buffer_offset + input_buffer_size + output_buffer_size = sdu_length + output_frame_size = output_buffer_size // num_channels + + for i in range(num_channels): + res = liblc3.lc3_encode( + encoders[i], + DEFAULT_PCM_FORMAT, + input_buffer_offset + DEFAULT_PCM_BYTES_PER_SAMPLE * i, + stride, + output_frame_size, + output_buffer_offset + output_frame_size * i, + ) + + if res != 0: + logging.error(f"Parsing failed, res={res}") + + # Extract decoded data from the output buffer + return bytes( + memory[output_buffer_offset : output_buffer_offset + output_buffer_size] + ) + + +async def lc3_source_task( + filename: str, + sdu_length: int, + frame_duration_us: int, + device: Device, + cis_handle: int, +) -> None: + with open(filename, 'rb') as f: + header = f.read(44) + assert header[8:12] == b'WAVE' + + pcm_num_channel, pcm_sample_rate, _byte_rate, _block_align, bits_per_sample = ( + struct.unpack(" None: + self.speaker = weakref.ref(speaker) + self.port = port + self.channel_socket = None + + async def start_http(self) -> None: + """Start the UI HTTP server.""" + + app = aiohttp.web.Application() + app.add_routes( + [ + aiohttp.web.get('/', self.get_static), + aiohttp.web.get('/index.html', self.get_static), + aiohttp.web.get('/channel', self.get_channel), + ] + ) + + runner = aiohttp.web.AppRunner(app) + await runner.setup() + site = aiohttp.web.TCPSite(runner, 'localhost', self.port) + print('UI HTTP server at ' + color(f'http://127.0.0.1:{self.port}', 'green')) + await site.start() + + async def get_static(self, request): + path = request.path + if path == '/': + path = '/index.html' + if path.endswith('.html'): + content_type = 'text/html' + elif path.endswith('.js'): + content_type = 'text/javascript' + elif path.endswith('.css'): + content_type = 'text/css' + elif path.endswith('.svg'): + content_type = 'image/svg+xml' + else: + content_type = 'text/plain' + text = ( + resources.files("bumble.apps.lea_unicast") + .joinpath(pathlib.Path(path).relative_to('/')) + .read_text(encoding="utf-8") + ) + return aiohttp.web.Response(text=text, content_type=content_type) + + async def get_channel(self, request): + ws = aiohttp.web.WebSocketResponse() + await ws.prepare(request) + + # Process messages until the socket is closed. + self.channel_socket = ws + async for message in ws: + if message.type == aiohttp.WSMsgType.TEXT: + logger.debug(f'<<< received message: {message.data}') + await self.on_message(message.data) + elif message.type == aiohttp.WSMsgType.ERROR: + logger.debug( + f'channel connection closed with exception {ws.exception()}' + ) + + self.channel_socket = None + logger.debug('--- channel connection closed') + + return ws + + async def on_message(self, message_str: str): + # Parse the message as JSON + message = json.loads(message_str) + + # Dispatch the message + message_type = message['type'] + message_params = message.get('params', {}) + handler = getattr(self, f'on_{message_type}_message') + if handler: + await handler(**message_params) + + async def on_hello_message(self): + await self.send_message( + 'hello', + bumble_version=bumble.__version__, + codec=self.speaker().codec, + streamState=self.speaker().stream_state.name, + ) + if connection := self.speaker().connection: + await self.send_message( + 'connection', + peer_address=connection.peer_address.to_string(False), + peer_name=connection.peer_name, + ) + + async def send_message(self, message_type: str, **kwargs) -> None: + if self.channel_socket is None: + return + + message = {'type': message_type, 'params': kwargs} + await self.channel_socket.send_json(message) + + async def send_audio(self, data: bytes) -> None: + if self.channel_socket is None: + return + + try: + await self.channel_socket.send_bytes(data) + except Exception as error: + logger.warning(f'exception while sending audio packet: {error}') + + +# ----------------------------------------------------------------------------- +class Speaker: + + def __init__( + self, + device_config_path: Optional[str], + ui_port: int, + transport: str, + lc3_input_file_path: str, + ): + self.device_config_path = device_config_path + self.transport = transport + self.lc3_input_file_path = lc3_input_file_path + + # Create an HTTP server for the UI + self.ui_server = UiServer(speaker=self, port=ui_port) + + async def run(self) -> None: + await self.ui_server.start_http() + + async with await open_transport(self.transport) as hci_transport: + # Create a device + if self.device_config_path: + device_config = DeviceConfiguration.from_file(self.device_config_path) + else: + device_config = DeviceConfiguration( + name="Bumble LE Headphone", + class_of_device=0x244418, + keystore="JsonKeyStore", + advertising_interval_min=25, + advertising_interval_max=25, + address=Address('F1:F2:F3:F4:F5:F6'), + ) + + device_config.le_enabled = True + device_config.cis_enabled = True + self.device = Device.from_config_with_hci( + device_config, hci_transport.source, hci_transport.sink + ) + + self.device.add_service( + bap.PublishedAudioCapabilitiesService( + supported_source_context=bap.ContextType(0xFFFF), + available_source_context=bap.ContextType(0xFFFF), + supported_sink_context=bap.ContextType(0xFFFF), # All context types + available_sink_context=bap.ContextType(0xFFFF), # All context types + sink_audio_locations=( + bap.AudioLocation.FRONT_LEFT | bap.AudioLocation.FRONT_RIGHT + ), + sink_pac=[_sink_pac_record()], + source_audio_locations=bap.AudioLocation.FRONT_LEFT, + source_pac=[_source_pac_record()], + ) + ) + + ascs = bap.AudioStreamControlService( + self.device, sink_ase_id=[1], source_ase_id=[2] + ) + self.device.add_service(ascs) + + advertising_data = bytes( + AdvertisingData( + [ + ( + AdvertisingData.COMPLETE_LOCAL_NAME, + bytes(device_config.name, 'utf-8'), + ), + ( + AdvertisingData.FLAGS, + bytes([AdvertisingData.LE_GENERAL_DISCOVERABLE_MODE_FLAG]), + ), + ( + AdvertisingData.INCOMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS, + bytes(bap.PublishedAudioCapabilitiesService.UUID), + ), + ] + ) + ) + bytes(bap.UnicastServerAdvertisingData()) + + def on_pdu(pdu: HCI_IsoDataPacket, ase: bap.AseStateMachine): + codec_config = ase.codec_specific_configuration + assert isinstance(codec_config, bap.CodecSpecificConfiguration) + pcm = decode( + codec_config.frame_duration.us, + codec_config.audio_channel_allocation.channel_count, + pdu.iso_sdu_fragment, + ) + self.device.abort_on('disconnection', self.ui_server.send_audio(pcm)) + + def on_ase_state_change(ase: bap.AseStateMachine) -> None: + if ase.state == bap.AseStateMachine.State.STREAMING: + codec_config = ase.codec_specific_configuration + assert isinstance(codec_config, bap.CodecSpecificConfiguration) + assert ase.cis_link + if ase.role == bap.AudioRole.SOURCE: + ase.cis_link.abort_on( + 'disconnection', + lc3_source_task( + filename=self.lc3_input_file_path, + sdu_length=( + codec_config.codec_frames_per_sdu + * codec_config.octets_per_codec_frame + ), + frame_duration_us=codec_config.frame_duration.us, + device=self.device, + cis_handle=ase.cis_link.handle, + ), + ) + else: + ase.cis_link.sink = functools.partial(on_pdu, ase=ase) + elif ase.state == bap.AseStateMachine.State.CODEC_CONFIGURED: + codec_config = ase.codec_specific_configuration + assert isinstance(codec_config, bap.CodecSpecificConfiguration) + if ase.role == bap.AudioRole.SOURCE: + setup_encoders( + codec_config.sampling_frequency.hz, + codec_config.frame_duration.us, + codec_config.audio_channel_allocation.channel_count, + ) + else: + setup_decoders( + codec_config.sampling_frequency.hz, + codec_config.frame_duration.us, + codec_config.audio_channel_allocation.channel_count, + ) + + for ase in ascs.ase_state_machines.values(): + ase.on('state_change', functools.partial(on_ase_state_change, ase=ase)) + + await self.device.power_on() + await self.device.create_advertising_set( + advertising_data=advertising_data, + auto_restart=True, + advertising_parameters=AdvertisingParameters( + primary_advertising_interval_min=100, + primary_advertising_interval_max=100, + ), + ) + + await hci_transport.source.terminated + + +@click.command() +@click.option( + '--ui-port', + 'ui_port', + metavar='HTTP_PORT', + default=DEFAULT_UI_PORT, + show_default=True, + help='HTTP port for the UI server', +) +@click.option('--device-config', metavar='FILENAME', help='Device configuration file') +@click.argument('transport') +@click.argument('lc3_file') +def speaker(ui_port: int, device_config: str, transport: str, lc3_file: str) -> None: + """Run the speaker.""" + + asyncio.run(Speaker(device_config, ui_port, transport, lc3_file).run()) + + +# ----------------------------------------------------------------------------- +def main(): + logging.basicConfig(level=os.environ.get('BUMBLE_LOGLEVEL', 'WARNING').upper()) + speaker() + + +# ----------------------------------------------------------------------------- +if __name__ == "__main__": + main() # pylint: disable=no-value-for-parameter diff --git a/apps/lea_unicast/index.html b/apps/lea_unicast/index.html new file mode 100644 index 00000000..fb1e61c9 --- /dev/null +++ b/apps/lea_unicast/index.html @@ -0,0 +1,68 @@ + + + + + + + + + +
+ +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/apps/lea_unicast/liblc3.wasm b/apps/lea_unicast/liblc3.wasm new file mode 100755 index 0000000000000000000000000000000000000000..e9051058cf187d5137fbb798ff910d18f717d938 GIT binary patch literal 158603 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 literal 0 HcmV?d00001 diff --git a/bumble/device.py b/bumble/device.py index 6bc945a3..8d6b8831 100644 --- a/bumble/device.py +++ b/bumble/device.py @@ -23,7 +23,13 @@ import asyncio import logging import secrets -from contextlib import asynccontextmanager, AsyncExitStack, closing +import sys +from contextlib import ( + asynccontextmanager, + AsyncExitStack, + closing, + AbstractAsyncContextManager, +) from dataclasses import dataclass, field from collections.abc import Iterable from typing import ( @@ -961,8 +967,9 @@ class ScoLink(CompositeEventEmitter): acl_connection: Connection handle: int link_type: int + sink: Optional[Callable[[HCI_SynchronousDataPacket], Any]] = None - def __post_init__(self): + def __post_init__(self) -> None: super().__init__() async def disconnect( @@ -984,8 +991,9 @@ class State(IntEnum): cis_id: int # CIS ID assigned by Central device cig_id: int # CIG ID assigned by Central device state: State = State.PENDING + sink: Optional[Callable[[HCI_IsoDataPacket], Any]] = None - def __post_init__(self): + def __post_init__(self) -> None: super().__init__() async def disconnect( @@ -1533,6 +1541,12 @@ def __init__( Address.ANY: [] } # Futures, by BD address OR [Futures] for Address.ANY + # In Python <= 3.9 + Rust Runtime, asyncio.Lock cannot be properly initiated. + if sys.version_info >= (3, 10): + self._cis_lock = asyncio.Lock() + else: + self._cis_lock = AsyncExitStack() + # Own address type cache self.connect_own_address_type = None @@ -3406,49 +3420,71 @@ async def create_cis(self, cis_acl_pairs: List[Tuple[int, int]]) -> List[CisLink for cis_handle, _ in cis_acl_pairs } - @watcher.on(self, 'cis_establishment') def on_cis_establishment(cis_link: CisLink) -> None: if pending_future := pending_cis_establishments.get(cis_link.handle): pending_future.set_result(cis_link) - result = await self.send_command( + def on_cis_establishment_failure(cis_handle: int, status: int) -> None: + if pending_future := pending_cis_establishments.get(cis_handle): + pending_future.set_exception(HCI_Error(status)) + + watcher.on(self, 'cis_establishment', on_cis_establishment) + watcher.on(self, 'cis_establishment_failure', on_cis_establishment_failure) + await self.send_command( HCI_LE_Create_CIS_Command( cis_connection_handle=[p[0] for p in cis_acl_pairs], acl_connection_handle=[p[1] for p in cis_acl_pairs], ), + check_result=True, ) - if result.status != HCI_COMMAND_STATUS_PENDING: - logger.warning( - 'HCI_LE_Create_CIS_Command failed: ' - f'{HCI_Constant.error_name(result.status)}' - ) - raise HCI_StatusError(result) return await asyncio.gather(*pending_cis_establishments.values()) # [LE only] @experimental('Only for testing.') async def accept_cis_request(self, handle: int) -> CisLink: - result = await self.send_command( - HCI_LE_Accept_CIS_Request_Command(connection_handle=handle), - ) - if result.status != HCI_COMMAND_STATUS_PENDING: - logger.warning( - 'HCI_LE_Accept_CIS_Request_Command failed: ' - f'{HCI_Constant.error_name(result.status)}' - ) - raise HCI_StatusError(result) + """[LE Only] Accepts an incoming CIS request. - pending_cis_establishment = asyncio.get_running_loop().create_future() + When the specified CIS handle is already created, this method returns the + existed CIS link object immediately. - with closing(EventWatcher()) as watcher: + Args: + handle: CIS handle to accept. - @watcher.on(self, 'cis_establishment') - def on_cis_establishment(cis_link: CisLink) -> None: - if cis_link.handle == handle: - pending_cis_establishment.set_result(cis_link) + Returns: + CIS link object on the given handle. + """ + if not (cis_link := self.cis_links.get(handle)): + raise InvalidStateError(f'No pending CIS request of handle {handle}') + + # There might be multiple ASE sharing a CIS channel. + # If one of them has accepted the request, the others should just leverage it. + async with self._cis_lock: + if cis_link.state == CisLink.State.ESTABLISHED: + return cis_link + + with closing(EventWatcher()) as watcher: + pending_establishment = asyncio.get_running_loop().create_future() + + def on_establishment() -> None: + pending_establishment.set_result(None) + + def on_establishment_failure(status: int) -> None: + pending_establishment.set_exception(HCI_Error(status)) + + watcher.on(cis_link, 'establishment', on_establishment) + watcher.on(cis_link, 'establishment_failure', on_establishment_failure) + + await self.send_command( + HCI_LE_Accept_CIS_Request_Command(connection_handle=handle), + check_result=True, + ) + + await pending_establishment + return cis_link - return await pending_cis_establishment + # Mypy believes this is reachable when context is an ExitStack. + raise InvalidStateError('Unreachable') # [LE only] @experimental('Only for testing.') @@ -3457,15 +3493,10 @@ async def reject_cis_request( handle: int, reason: int = HCI_REMOTE_USER_TERMINATED_CONNECTION_ERROR, ) -> None: - result = await self.send_command( + await self.send_command( HCI_LE_Reject_CIS_Request_Command(connection_handle=handle, reason=reason), + check_result=True, ) - if result.status != HCI_COMMAND_STATUS_PENDING: - logger.warning( - 'HCI_LE_Reject_CIS_Request_Command failed: ' - f'{HCI_Constant.error_name(result.status)}' - ) - raise HCI_StatusError(result) async def get_remote_le_features(self, connection: Connection) -> LeFeatureMask: """[LE Only] Reads remote LE supported features. @@ -3485,11 +3516,17 @@ def on_le_remote_features(handle: int, features: int): if handle == connection.handle: read_feature_future.set_result(LeFeatureMask(features)) + def on_failure(handle: int, status: int): + if handle == connection.handle: + read_feature_future.set_exception(HCI_Error(status)) + watcher.on(self.host, 'le_remote_features', on_le_remote_features) + watcher.on(self.host, 'le_remote_features_failure', on_failure) await self.send_command( HCI_LE_Read_Remote_Features_Command( connection_handle=connection.handle ), + check_result=True, ) return await read_feature_future @@ -4111,8 +4148,8 @@ def on_sco_connection_failure( @host_event_handler @experimental('Only for testing') def on_sco_packet(self, sco_handle: int, packet: HCI_SynchronousDataPacket) -> None: - if sco_link := self.sco_links.get(sco_handle): - sco_link.emit('pdu', packet) + if (sco_link := self.sco_links.get(sco_handle)) and sco_link.sink: + sco_link.sink(packet) # [LE only] @host_event_handler @@ -4168,15 +4205,15 @@ def on_cis_establishment(self, cis_handle: int) -> None: def on_cis_establishment_failure(self, cis_handle: int, status: int) -> None: logger.debug(f'*** CIS Establishment Failure: cis=[0x{cis_handle:04X}] ***') if cis_link := self.cis_links.pop(cis_handle): - cis_link.emit('establishment_failure') + cis_link.emit('establishment_failure', status) self.emit('cis_establishment_failure', cis_handle, status) # [LE only] @host_event_handler @experimental('Only for testing') def on_iso_packet(self, handle: int, packet: HCI_IsoDataPacket) -> None: - if cis_link := self.cis_links.get(handle): - cis_link.emit('pdu', packet) + if (cis_link := self.cis_links.get(handle)) and cis_link.sink: + cis_link.sink(packet) @host_event_handler @with_connection_from_handle diff --git a/bumble/hci.py b/bumble/hci.py index fba89515..9ef40bf2 100644 --- a/bumble/hci.py +++ b/bumble/hci.py @@ -23,7 +23,7 @@ import logging import secrets import struct -from typing import Any, Callable, Dict, Iterable, List, Optional, Type, Union +from typing import Any, Callable, Dict, Iterable, List, Optional, Type, Union, ClassVar from bumble import crypto from .colors import color @@ -2003,7 +2003,7 @@ class HCI_Packet: Abstract Base class for HCI packets ''' - hci_packet_type: int + hci_packet_type: ClassVar[int] @staticmethod def from_bytes(packet: bytes) -> HCI_Packet: @@ -6192,12 +6192,23 @@ def __str__(self) -> str: # ----------------------------------------------------------------------------- +@dataclasses.dataclass class HCI_IsoDataPacket(HCI_Packet): ''' See Bluetooth spec @ 5.4.5 HCI ISO Data Packets ''' - hci_packet_type = HCI_ISO_DATA_PACKET + hci_packet_type: ClassVar[int] = HCI_ISO_DATA_PACKET + + connection_handle: int + data_total_length: int + iso_sdu_fragment: bytes + pb_flag: int + ts_flag: int = 0 + time_stamp: Optional[int] = None + packet_sequence_number: Optional[int] = None + iso_sdu_length: Optional[int] = None + packet_status_flag: Optional[int] = None @staticmethod def from_bytes(packet: bytes) -> HCI_IsoDataPacket: @@ -6241,28 +6252,6 @@ def from_bytes(packet: bytes) -> HCI_IsoDataPacket: iso_sdu_fragment=iso_sdu_fragment, ) - def __init__( - self, - connection_handle: int, - pb_flag: int, - ts_flag: int, - data_total_length: int, - time_stamp: Optional[int], - packet_sequence_number: Optional[int], - iso_sdu_length: Optional[int], - packet_status_flag: Optional[int], - iso_sdu_fragment: bytes, - ) -> None: - self.connection_handle = connection_handle - self.pb_flag = pb_flag - self.ts_flag = ts_flag - self.data_total_length = data_total_length - self.time_stamp = time_stamp - self.packet_sequence_number = packet_sequence_number - self.iso_sdu_length = iso_sdu_length - self.packet_status_flag = packet_status_flag - self.iso_sdu_fragment = iso_sdu_fragment - def __bytes__(self) -> bytes: return self.to_bytes() diff --git a/bumble/host.py b/bumble/host.py index 4223d0c4..64b66688 100644 --- a/bumble/host.py +++ b/bumble/host.py @@ -721,14 +721,16 @@ def on_hci_number_of_completed_packets_event(self, event): for connection_handle, num_completed_packets in zip( event.connection_handles, event.num_completed_packets ): - if not (connection := self.connections.get(connection_handle)): + if connection := self.connections.get(connection_handle): + connection.acl_packet_queue.on_packets_completed(num_completed_packets) + elif not ( + self.cis_links.get(connection_handle) + or self.sco_links.get(connection_handle) + ): logger.warning( 'received packet completion event for unknown handle ' f'0x{connection_handle:04X}' ) - continue - - connection.acl_packet_queue.on_packets_completed(num_completed_packets) # Classic only def on_hci_connection_request_event(self, event): diff --git a/bumble/profiles/bap.py b/bumble/profiles/bap.py index b54ad1dc..c0123b11 100644 --- a/bumble/profiles/bap.py +++ b/bumble/profiles/bap.py @@ -78,6 +78,10 @@ class AudioLocation(enum.IntFlag): LEFT_SURROUND = 0x04000000 RIGHT_SURROUND = 0x08000000 + @property + def channel_count(self) -> int: + return bin(self.value).count('1') + class AudioInputType(enum.IntEnum): '''Bluetooth Assigned Numbers, Section 6.12.2 - Audio Input Type''' @@ -218,6 +222,13 @@ class FrameDuration(enum.IntEnum): DURATION_7500_US = 0x00 DURATION_10000_US = 0x01 + @property + def us(self) -> int: + return { + FrameDuration.DURATION_7500_US: 7500, + FrameDuration.DURATION_10000_US: 10000, + }[self] + class SupportedFrameDuration(enum.IntFlag): '''Bluetooth Assigned Numbers, Section 6.12.4.2 - Frame Duration''' @@ -534,7 +545,7 @@ class Type(enum.IntEnum): supported_sampling_frequencies: SupportedSamplingFrequency supported_frame_durations: SupportedFrameDuration - supported_audio_channel_counts: Sequence[int] + supported_audio_channel_count: Sequence[int] min_octets_per_codec_frame: int max_octets_per_codec_frame: int supported_max_codec_frames_per_sdu: int @@ -543,7 +554,7 @@ class Type(enum.IntEnum): def from_bytes(cls, data: bytes) -> CodecSpecificCapabilities: offset = 0 # Allowed default values. - supported_audio_channel_counts = [1] + supported_audio_channel_count = [1] supported_max_codec_frames_per_sdu = 1 while offset < len(data): length, type = struct.unpack_from('BB', data, offset) @@ -556,7 +567,7 @@ def from_bytes(cls, data: bytes) -> CodecSpecificCapabilities: elif type == CodecSpecificCapabilities.Type.FRAME_DURATION: supported_frame_durations = SupportedFrameDuration(value) elif type == CodecSpecificCapabilities.Type.AUDIO_CHANNEL_COUNT: - supported_audio_channel_counts = bits_to_channel_counts(value) + supported_audio_channel_count = bits_to_channel_counts(value) elif type == CodecSpecificCapabilities.Type.OCTETS_PER_FRAME: min_octets_per_sample = value & 0xFFFF max_octets_per_sample = value >> 16 @@ -567,7 +578,7 @@ def from_bytes(cls, data: bytes) -> CodecSpecificCapabilities: return CodecSpecificCapabilities( supported_sampling_frequencies=supported_sampling_frequencies, supported_frame_durations=supported_frame_durations, - supported_audio_channel_counts=supported_audio_channel_counts, + supported_audio_channel_count=supported_audio_channel_count, min_octets_per_codec_frame=min_octets_per_sample, max_octets_per_codec_frame=max_octets_per_sample, supported_max_codec_frames_per_sdu=supported_max_codec_frames_per_sdu, @@ -584,7 +595,7 @@ def __bytes__(self) -> bytes: self.supported_frame_durations, 2, CodecSpecificCapabilities.Type.AUDIO_CHANNEL_COUNT, - channel_counts_to_bits(self.supported_audio_channel_counts), + channel_counts_to_bits(self.supported_audio_channel_count), 5, CodecSpecificCapabilities.Type.OCTETS_PER_FRAME, self.min_octets_per_codec_frame, @@ -870,15 +881,22 @@ def on_cis_request( cig_id: int, cis_id: int, ) -> None: - if cis_id == self.cis_id and self.state == self.State.ENABLING: + if ( + cig_id == self.cig_id + and cis_id == self.cis_id + and self.state == self.State.ENABLING + ): acl_connection.abort_on( 'flush', self.service.device.accept_cis_request(cis_handle) ) def on_cis_establishment(self, cis_link: device.CisLink) -> None: - if cis_link.cis_id == self.cis_id and self.state == self.State.ENABLING: - self.state = self.State.STREAMING - self.cis_link = cis_link + if ( + cis_link.cig_id == self.cig_id + and cis_link.cis_id == self.cis_id + and self.state == self.State.ENABLING + ): + cis_link.on('disconnection', self.on_cis_disconnection) async def post_cis_established(): await self.service.device.send_command( @@ -891,9 +909,15 @@ async def post_cis_established(): codec_configuration=b'', ) ) + if self.role == AudioRole.SINK: + self.state = self.State.STREAMING await self.service.device.notify_subscribers(self, self.value) cis_link.acl_connection.abort_on('flush', post_cis_established()) + self.cis_link = cis_link + + def on_cis_disconnection(self, _reason) -> None: + self.cis_link = None def on_config_codec( self, @@ -991,11 +1015,17 @@ def on_disable(self) -> Tuple[AseResponseCode, AseReasonCode]: AseResponseCode.INVALID_ASE_STATE_MACHINE_TRANSITION, AseReasonCode.NONE, ) - self.state = self.State.DISABLING + if self.role == AudioRole.SINK: + self.state = self.State.QOS_CONFIGURED + else: + self.state = self.State.DISABLING return (AseResponseCode.SUCCESS, AseReasonCode.NONE) def on_receiver_stop_ready(self) -> Tuple[AseResponseCode, AseReasonCode]: - if self.state != AseStateMachine.State.DISABLING: + if ( + self.role != AudioRole.SOURCE + or self.state != AseStateMachine.State.DISABLING + ): return ( AseResponseCode.INVALID_ASE_STATE_MACHINE_TRANSITION, AseReasonCode.NONE, @@ -1046,6 +1076,7 @@ def state(self) -> State: def state(self, new_state: State) -> None: logger.debug(f'{self} state change -> {colors.color(new_state.name, "cyan")}') self._state = new_state + self.emit('state_change') @property def value(self): @@ -1118,6 +1149,7 @@ class AudioStreamControlService(gatt.TemplateService): ase_state_machines: Dict[int, AseStateMachine] ase_control_point: gatt.Characteristic + _active_client: Optional[device.Connection] = None def __init__( self, @@ -1155,7 +1187,16 @@ def on_operation(self, opcode: ASE_Operation.Opcode, ase_id: int, args): else: return (ase_id, AseResponseCode.INVALID_ASE_ID, AseReasonCode.NONE) + def _on_client_disconnected(self, _reason: int) -> None: + for ase in self.ase_state_machines.values(): + ase.state = AseStateMachine.State.IDLE + self._active_client = None + def on_write_ase_control_point(self, connection, data): + if not self._active_client and connection: + self._active_client = connection + connection.once('disconnection', self._on_client_disconnected) + operation = ASE_Operation.from_bytes(data) responses = [] logger.debug(f'*** ASCS Write {operation} ***') diff --git a/examples/run_hfp_gateway.py b/examples/run_hfp_gateway.py index 8e596f80..851f97cf 100644 --- a/examples/run_hfp_gateway.py +++ b/examples/run_hfp_gateway.py @@ -26,7 +26,7 @@ from typing import Optional import bumble.core -from bumble.device import Device +from bumble.device import Device, ScoLink from bumble.transport import open_transport_or_link from bumble.core import ( BT_BR_EDR_TRANSPORT, @@ -217,11 +217,11 @@ def on_dlc(dlc: rfcomm.DLC): 1: hfp.make_ag_sdp_records(1, channel, configuration) } - def on_sco_connection(sco_link): + def on_sco_connection(sco_link: ScoLink): assert ag_protocol on_sco_state_change(ag_protocol.active_codec) sco_link.on('disconnection', lambda _: on_sco_state_change(0)) - sco_link.on('pdu', on_sco_packet) + sco_link.sink = on_sco_packet device.on('sco_connection', on_sco_connection) if len(sys.argv) >= 4: diff --git a/examples/run_unicast_server.py b/examples/run_unicast_server.py index 60d2f4ae..95ae5510 100644 --- a/examples/run_unicast_server.py +++ b/examples/run_unicast_server.py @@ -16,20 +16,28 @@ # Imports # ----------------------------------------------------------------------------- import asyncio +import datetime +import functools import logging import sys import os +import io import struct import secrets + +from typing import Dict + from bumble.core import AdvertisingData -from bumble.device import Device, CisLink +from bumble.device import Device from bumble.hci import ( CodecID, CodingFormat, HCI_IsoDataPacket, ) from bumble.profiles.bap import ( + AseStateMachine, UnicastServerAdvertisingData, + CodecSpecificConfiguration, CodecSpecificCapabilities, ContextType, AudioLocation, @@ -45,6 +53,32 @@ from bumble.transport import open_transport_or_link +def _sink_pac_record() -> PacRecord: + return PacRecord( + coding_format=CodingFormat(CodecID.LC3), + codec_specific_capabilities=CodecSpecificCapabilities( + supported_sampling_frequencies=( + SupportedSamplingFrequency.FREQ_8000 + | SupportedSamplingFrequency.FREQ_16000 + | SupportedSamplingFrequency.FREQ_24000 + | SupportedSamplingFrequency.FREQ_32000 + | SupportedSamplingFrequency.FREQ_48000 + ), + supported_frame_durations=( + SupportedFrameDuration.DURATION_7500_US_SUPPORTED + | SupportedFrameDuration.DURATION_10000_US_SUPPORTED + ), + supported_audio_channel_count=[1, 2], + min_octets_per_codec_frame=26, + max_octets_per_codec_frame=240, + supported_max_codec_frames_per_sdu=2, + ), + ) + + +file_outputs: Dict[AseStateMachine, io.BufferedWriter] = {} + + # ----------------------------------------------------------------------------- async def main() -> None: if len(sys.argv) < 3: @@ -71,49 +105,17 @@ async def main() -> None: PublishedAudioCapabilitiesService( supported_source_context=ContextType.PROHIBITED, available_source_context=ContextType.PROHIBITED, - supported_sink_context=ContextType.MEDIA, - available_sink_context=ContextType.MEDIA, + supported_sink_context=ContextType(0xFF), # All context types + available_sink_context=ContextType(0xFF), # All context types sink_audio_locations=( AudioLocation.FRONT_LEFT | AudioLocation.FRONT_RIGHT ), - sink_pac=[ - # Codec Capability Setting 16_2 - PacRecord( - coding_format=CodingFormat(CodecID.LC3), - codec_specific_capabilities=CodecSpecificCapabilities( - supported_sampling_frequencies=( - SupportedSamplingFrequency.FREQ_16000 - ), - supported_frame_durations=( - SupportedFrameDuration.DURATION_10000_US_SUPPORTED - ), - supported_audio_channel_counts=[1], - min_octets_per_codec_frame=40, - max_octets_per_codec_frame=40, - supported_max_codec_frames_per_sdu=1, - ), - ), - # Codec Capability Setting 24_2 - PacRecord( - coding_format=CodingFormat(CodecID.LC3), - codec_specific_capabilities=CodecSpecificCapabilities( - supported_sampling_frequencies=( - SupportedSamplingFrequency.FREQ_48000 - ), - supported_frame_durations=( - SupportedFrameDuration.DURATION_10000_US_SUPPORTED - ), - supported_audio_channel_counts=[1], - min_octets_per_codec_frame=120, - max_octets_per_codec_frame=120, - supported_max_codec_frames_per_sdu=1, - ), - ), - ], + sink_pac=[_sink_pac_record()], ) ) - device.add_service(AudioStreamControlService(device, sink_ase_id=[1, 2])) + ascs = AudioStreamControlService(device, sink_ase_id=[1], source_ase_id=[2]) + device.add_service(ascs) advertising_data = ( bytes( @@ -143,44 +145,57 @@ async def main() -> None: + csis.get_advertising_data() + bytes(UnicastServerAdvertisingData()) ) - subprocess = await asyncio.create_subprocess_shell( - f'dlc3 | ffplay pipe:0', - stdin=asyncio.subprocess.PIPE, - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, - ) - - stdin = subprocess.stdin - assert stdin - - # Write a fake LC3 header to dlc3. - stdin.write( - bytes([0x1C, 0xCC]) # Header. - + struct.pack( - ' None: + if state != AseStateMachine.State.STREAMING: + if file_output := file_outputs.pop(ase): + file_output.close() + else: + file_output = open(f'{datetime.datetime.now().isoformat()}.lc3', 'wb') + codec_configuration = ase.codec_specific_configuration + assert isinstance(codec_configuration, CodecSpecificConfiguration) + # Write a LC3 header. + file_output.write( + bytes([0x1C, 0xCC]) # Header. + + struct.pack( + ' None: supported_frame_durations=( SupportedFrameDuration.DURATION_10000_US_SUPPORTED ), - supported_audio_channel_counts=[1], + supported_audio_channel_count=[1], min_octets_per_codec_frame=120, max_octets_per_codec_frame=120, supported_max_codec_frames_per_sdu=1, diff --git a/setup.cfg b/setup.cfg index 129ae51f..4dd551e4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -96,6 +96,7 @@ development = types-appdirs >= 1.4.3 types-invoke >= 1.7.3 types-protobuf >= 4.21.0 + wasmtime == 20.0.0 avatar = pandora-avatar == 0.0.9 rootcanal == 1.10.0 ; python_version>='3.10' diff --git a/tests/bap_test.py b/tests/bap_test.py index bc223c14..0b6db1a7 100644 --- a/tests/bap_test.py +++ b/tests/bap_test.py @@ -72,7 +72,7 @@ def test_codec_specific_capabilities() -> None: cap = CodecSpecificCapabilities( supported_sampling_frequencies=SAMPLE_FREQUENCY, supported_frame_durations=FRAME_SURATION, - supported_audio_channel_counts=AUDIO_CHANNEL_COUNTS, + supported_audio_channel_count=AUDIO_CHANNEL_COUNTS, min_octets_per_codec_frame=40, max_octets_per_codec_frame=40, supported_max_codec_frames_per_sdu=1, @@ -88,7 +88,7 @@ def test_pac_record() -> None: cap = CodecSpecificCapabilities( supported_sampling_frequencies=SAMPLE_FREQUENCY, supported_frame_durations=FRAME_SURATION, - supported_audio_channel_counts=AUDIO_CHANNEL_COUNTS, + supported_audio_channel_count=AUDIO_CHANNEL_COUNTS, min_octets_per_codec_frame=40, max_octets_per_codec_frame=40, supported_max_codec_frames_per_sdu=1, @@ -216,7 +216,7 @@ async def test_pacs(): supported_frame_durations=( SupportedFrameDuration.DURATION_10000_US_SUPPORTED ), - supported_audio_channel_counts=[1], + supported_audio_channel_count=[1], min_octets_per_codec_frame=40, max_octets_per_codec_frame=40, supported_max_codec_frames_per_sdu=1, @@ -232,7 +232,7 @@ async def test_pacs(): supported_frame_durations=( SupportedFrameDuration.DURATION_10000_US_SUPPORTED ), - supported_audio_channel_counts=[1], + supported_audio_channel_count=[1], min_octets_per_codec_frame=60, max_octets_per_codec_frame=60, supported_max_codec_frames_per_sdu=1, diff --git a/tests/device_test.py b/tests/device_test.py index 5d872826..814fed6f 100644 --- a/tests/device_test.py +++ b/tests/device_test.py @@ -16,6 +16,7 @@ # Imports # ----------------------------------------------------------------------------- import asyncio +import functools import logging import os from types import LambdaType @@ -35,12 +36,14 @@ HCI_COMMAND_STATUS_PENDING, HCI_CREATE_CONNECTION_COMMAND, HCI_SUCCESS, + HCI_CONNECTION_FAILED_TO_BE_ESTABLISHED_ERROR, Address, OwnAddressType, HCI_Command_Complete_Event, HCI_Command_Status_Event, HCI_Connection_Complete_Event, HCI_Connection_Request_Event, + HCI_Error, HCI_Packet, ) from bumble.gatt import ( @@ -52,6 +55,10 @@ from .test_utils import TwoDevices, async_barrier +# ----------------------------------------------------------------------------- +# Constants +# ----------------------------------------------------------------------------- +_TIMEOUT = 0.1 # ----------------------------------------------------------------------------- # Logging @@ -385,6 +392,29 @@ async def test_get_remote_le_features(): assert (await devices.connections[0].get_remote_le_features()) is not None +# ----------------------------------------------------------------------------- +@pytest.mark.asyncio +async def test_get_remote_le_features_failed(): + devices = TwoDevices() + await devices.setup_connection() + + def on_hci_le_read_remote_features_complete_event(event): + devices[0].host.emit( + 'le_remote_features_failure', + event.connection_handle, + HCI_CONNECTION_FAILED_TO_BE_ESTABLISHED_ERROR, + ) + + devices[0].host.on_hci_le_read_remote_features_complete_event = ( + on_hci_le_read_remote_features_complete_event + ) + + with pytest.raises(HCI_Error): + await asyncio.wait_for( + devices.connections[0].get_remote_le_features(), _TIMEOUT + ) + + # ----------------------------------------------------------------------------- @pytest.mark.asyncio async def test_cis(): @@ -433,6 +463,65 @@ def on_cis_request( await cis_links[1].disconnect() +# ----------------------------------------------------------------------------- +@pytest.mark.asyncio +async def test_cis_setup_failure(): + devices = TwoDevices() + await devices.setup_connection() + + cis_requests = asyncio.Queue() + + def on_cis_request( + acl_connection: Connection, + cis_handle: int, + cig_id: int, + cis_id: int, + ): + del acl_connection, cig_id, cis_id + cis_requests.put_nowait(cis_handle) + + devices[1].on('cis_request', on_cis_request) + + cis_handles = await devices[0].setup_cig( + cig_id=1, + cis_id=[2], + sdu_interval=(0, 0), + framing=0, + max_sdu=(0, 0), + retransmission_number=0, + max_transport_latency=(0, 0), + ) + assert len(cis_handles) == 1 + + cis_create_task = asyncio.create_task( + devices[0].create_cis( + [ + (cis_handles[0], devices.connections[0].handle), + ] + ) + ) + + def on_hci_le_cis_established_event(host, event): + host.emit( + 'cis_establishment_failure', + event.connection_handle, + HCI_CONNECTION_FAILED_TO_BE_ESTABLISHED_ERROR, + ) + + for device in devices: + device.host.on_hci_le_cis_established_event = functools.partial( + on_hci_le_cis_established_event, device.host + ) + + cis_request = await asyncio.wait_for(cis_requests.get(), _TIMEOUT) + + with pytest.raises(HCI_Error): + await asyncio.wait_for(devices[1].accept_cis_request(cis_request), _TIMEOUT) + + with pytest.raises(HCI_Error): + await asyncio.wait_for(cis_create_task, _TIMEOUT) + + # ----------------------------------------------------------------------------- def test_gatt_services_with_gas(): device = Device(host=Host(None, None))