Merge pull request #803 from zxzxwu/avdtp

AVDTP: Migrate enums
This commit is contained in:
zxzxwu
2025-10-23 13:45:48 +08:00
committed by GitHub
3 changed files with 232 additions and 238 deletions

View File

@@ -35,13 +35,14 @@ from typing import (
cast, cast,
) )
from typing_extensions import override
from bumble import a2dp, device, hci, l2cap, sdp, utils from bumble import a2dp, device, hci, l2cap, sdp, utils
from bumble.colors import color from bumble.colors import color
from bumble.core import ( from bumble.core import (
BT_ADVANCED_AUDIO_DISTRIBUTION_SERVICE, BT_ADVANCED_AUDIO_DISTRIBUTION_SERVICE,
InvalidStateError, InvalidStateError,
ProtocolError, ProtocolError,
name_or_number,
) )
from bumble.rtp import MediaPacket from bumble.rtp import MediaPacket
@@ -62,74 +63,72 @@ AVDTP_PSM = 0x0019
AVDTP_DEFAULT_RTX_SIG_TIMER = 5 # Seconds AVDTP_DEFAULT_RTX_SIG_TIMER = 5 # Seconds
# Signal Identifiers (AVDTP spec - 8.5 Signal Command Set) # Signal Identifiers (AVDTP spec - 8.5 Signal Command Set)
AVDTP_DISCOVER = 0x01 class SignalIdentifier(hci.SpecableEnum):
AVDTP_GET_CAPABILITIES = 0x02 DISCOVER = 0x01
AVDTP_SET_CONFIGURATION = 0x03 GET_CAPABILITIES = 0x02
AVDTP_GET_CONFIGURATION = 0x04 SET_CONFIGURATION = 0x03
AVDTP_RECONFIGURE = 0x05 GET_CONFIGURATION = 0x04
AVDTP_OPEN = 0x06 RECONFIGURE = 0x05
AVDTP_START = 0x07 OPEN = 0x06
AVDTP_CLOSE = 0x08 START = 0x07
AVDTP_SUSPEND = 0x09 CLOSE = 0x08
AVDTP_ABORT = 0x0A SUSPEND = 0x09
AVDTP_SECURITY_CONTROL = 0x0B ABORT = 0x0A
AVDTP_GET_ALL_CAPABILITIES = 0x0C SECURITY_CONTROL = 0x0B
AVDTP_DELAYREPORT = 0x0D GET_ALL_CAPABILITIES = 0x0C
DELAYREPORT = 0x0D
AVDTP_SIGNAL_NAMES = { AVDTP_DISCOVER = SignalIdentifier.DISCOVER
AVDTP_DISCOVER: 'AVDTP_DISCOVER', AVDTP_GET_CAPABILITIES = SignalIdentifier.GET_CAPABILITIES
AVDTP_GET_CAPABILITIES: 'AVDTP_GET_CAPABILITIES', AVDTP_SET_CONFIGURATION = SignalIdentifier.SET_CONFIGURATION
AVDTP_SET_CONFIGURATION: 'AVDTP_SET_CONFIGURATION', AVDTP_GET_CONFIGURATION = SignalIdentifier.GET_CONFIGURATION
AVDTP_GET_CONFIGURATION: 'AVDTP_GET_CONFIGURATION', AVDTP_RECONFIGURE = SignalIdentifier.RECONFIGURE
AVDTP_RECONFIGURE: 'AVDTP_RECONFIGURE', AVDTP_OPEN = SignalIdentifier.OPEN
AVDTP_OPEN: 'AVDTP_OPEN', AVDTP_START = SignalIdentifier.START
AVDTP_START: 'AVDTP_START', AVDTP_CLOSE = SignalIdentifier.CLOSE
AVDTP_CLOSE: 'AVDTP_CLOSE', AVDTP_SUSPEND = SignalIdentifier.SUSPEND
AVDTP_SUSPEND: 'AVDTP_SUSPEND', AVDTP_ABORT = SignalIdentifier.ABORT
AVDTP_ABORT: 'AVDTP_ABORT', AVDTP_SECURITY_CONTROL = SignalIdentifier.SECURITY_CONTROL
AVDTP_SECURITY_CONTROL: 'AVDTP_SECURITY_CONTROL', AVDTP_GET_ALL_CAPABILITIES = SignalIdentifier.GET_ALL_CAPABILITIES
AVDTP_GET_ALL_CAPABILITIES: 'AVDTP_GET_ALL_CAPABILITIES', AVDTP_DELAYREPORT = SignalIdentifier.DELAYREPORT
AVDTP_DELAYREPORT: 'AVDTP_DELAYREPORT'
}
# Error codes (AVDTP spec - 8.20.6.2 ERROR_CODE tables) class ErrorCode(hci.SpecableEnum):
AVDTP_BAD_HEADER_FORMAT_ERROR = 0x01 '''Error codes (AVDTP spec - 8.20.6.2 ERROR_CODE tables)'''
AVDTP_BAD_LENGTH_ERROR = 0x11 BAD_HEADER_FORMAT = 0x01
AVDTP_BAD_ACP_SEID_ERROR = 0x12 BAD_LENGTH = 0x11
AVDTP_SEP_IN_USE_ERROR = 0x13 BAD_ACP_SEID = 0x12
AVDTP_SEP_NOT_IN_USE_ERROR = 0x14 SEP_IN_USE = 0x13
AVDTP_BAD_SERV_CATEGORY_ERROR = 0x17 SEP_NOT_IN_USE = 0x14
AVDTP_BAD_PAYLOAD_FORMAT_ERROR = 0x18 BAD_SERV_CATEGORY = 0x17
AVDTP_NOT_SUPPORTED_COMMAND_ERROR = 0x19 BAD_PAYLOAD_FORMAT = 0x18
AVDTP_INVALID_CAPABILITIES_ERROR = 0x1A NOT_SUPPORTED_COMMAND = 0x19
AVDTP_BAD_RECOVERY_TYPE_ERROR = 0x22 INVALID_CAPABILITIES = 0x1A
AVDTP_BAD_MEDIA_TRANSPORT_FORMAT_ERROR = 0x23 BAD_RECOVERY_TYPE = 0x22
AVDTP_BAD_RECOVERY_FORMAT_ERROR = 0x25 BAD_MEDIA_TRANSPORT_FORMAT = 0x23
AVDTP_BAD_ROHC_FORMAT_ERROR = 0x26 BAD_RECOVERY_FORMAT = 0x25
AVDTP_BAD_CP_FORMAT_ERROR = 0x27 BAD_ROHC_FORMAT = 0x26
AVDTP_BAD_MULTIPLEXING_FORMAT_ERROR = 0x28 BAD_CP_FORMAT = 0x27
AVDTP_UNSUPPORTED_CONFIGURATION_ERROR = 0x29 BAD_MULTIPLEXING_FORMAT = 0x28
AVDTP_BAD_STATE_ERROR = 0x31 UNSUPPORTED_CONFIGURATION = 0x29
BAD_STATE = 0x31
AVDTP_ERROR_NAMES = { AVDTP_BAD_HEADER_FORMAT_ERROR = ErrorCode.BAD_HEADER_FORMAT
AVDTP_BAD_HEADER_FORMAT_ERROR: 'AVDTP_BAD_HEADER_FORMAT_ERROR', AVDTP_BAD_LENGTH_ERROR = ErrorCode.BAD_LENGTH
AVDTP_BAD_LENGTH_ERROR: 'AVDTP_BAD_LENGTH_ERROR', AVDTP_BAD_ACP_SEID_ERROR = ErrorCode.BAD_ACP_SEID
AVDTP_BAD_ACP_SEID_ERROR: 'AVDTP_BAD_ACP_SEID_ERROR', AVDTP_SEP_IN_USE_ERROR = ErrorCode.SEP_IN_USE
AVDTP_SEP_IN_USE_ERROR: 'AVDTP_SEP_IN_USE_ERROR', AVDTP_SEP_NOT_IN_USE_ERROR = ErrorCode.SEP_NOT_IN_USE
AVDTP_SEP_NOT_IN_USE_ERROR: 'AVDTP_SEP_NOT_IN_USE_ERROR', AVDTP_BAD_SERV_CATEGORY_ERROR = ErrorCode.BAD_SERV_CATEGORY
AVDTP_BAD_SERV_CATEGORY_ERROR: 'AVDTP_BAD_SERV_CATEGORY_ERROR', AVDTP_BAD_PAYLOAD_FORMAT_ERROR = ErrorCode.BAD_PAYLOAD_FORMAT
AVDTP_BAD_PAYLOAD_FORMAT_ERROR: 'AVDTP_BAD_PAYLOAD_FORMAT_ERROR', AVDTP_NOT_SUPPORTED_COMMAND_ERROR = ErrorCode.NOT_SUPPORTED_COMMAND
AVDTP_NOT_SUPPORTED_COMMAND_ERROR: 'AVDTP_NOT_SUPPORTED_COMMAND_ERROR', AVDTP_INVALID_CAPABILITIES_ERROR = ErrorCode.INVALID_CAPABILITIES
AVDTP_INVALID_CAPABILITIES_ERROR: 'AVDTP_INVALID_CAPABILITIES_ERROR', AVDTP_BAD_RECOVERY_TYPE_ERROR = ErrorCode.BAD_RECOVERY_TYPE
AVDTP_BAD_RECOVERY_TYPE_ERROR: 'AVDTP_BAD_RECOVERY_TYPE_ERROR', AVDTP_BAD_MEDIA_TRANSPORT_FORMAT_ERROR = ErrorCode.BAD_MEDIA_TRANSPORT_FORMAT
AVDTP_BAD_MEDIA_TRANSPORT_FORMAT_ERROR: 'AVDTP_BAD_MEDIA_TRANSPORT_FORMAT_ERROR', AVDTP_BAD_RECOVERY_FORMAT_ERROR = ErrorCode.BAD_RECOVERY_FORMAT
AVDTP_BAD_RECOVERY_FORMAT_ERROR: 'AVDTP_BAD_RECOVERY_FORMAT_ERROR', AVDTP_BAD_ROHC_FORMAT_ERROR = ErrorCode.BAD_ROHC_FORMAT
AVDTP_BAD_ROHC_FORMAT_ERROR: 'AVDTP_BAD_ROHC_FORMAT_ERROR', AVDTP_BAD_CP_FORMAT_ERROR = ErrorCode.BAD_CP_FORMAT
AVDTP_BAD_CP_FORMAT_ERROR: 'AVDTP_BAD_CP_FORMAT_ERROR', AVDTP_BAD_MULTIPLEXING_FORMAT_ERROR = ErrorCode.BAD_MULTIPLEXING_FORMAT
AVDTP_BAD_MULTIPLEXING_FORMAT_ERROR: 'AVDTP_BAD_MULTIPLEXING_FORMAT_ERROR', AVDTP_UNSUPPORTED_CONFIGURATION_ERROR = ErrorCode.UNSUPPORTED_CONFIGURATION
AVDTP_UNSUPPORTED_CONFIGURATION_ERROR: 'AVDTP_UNSUPPORTED_CONFIGURATION_ERROR', AVDTP_BAD_STATE_ERROR = ErrorCode.BAD_STATE
AVDTP_BAD_STATE_ERROR: 'AVDTP_BAD_STATE_ERROR'
}
class MediaType(utils.OpenIntEnum): class MediaType(utils.OpenIntEnum):
AUDIO = 0x00 AUDIO = 0x00
@@ -140,52 +139,42 @@ AVDTP_AUDIO_MEDIA_TYPE = MediaType.AUDIO
AVDTP_VIDEO_MEDIA_TYPE = MediaType.VIDEO AVDTP_VIDEO_MEDIA_TYPE = MediaType.VIDEO
AVDTP_MULTIMEDIA_MEDIA_TYPE = MediaType.MULTIMEDIA AVDTP_MULTIMEDIA_MEDIA_TYPE = MediaType.MULTIMEDIA
# TSEP (AVDTP spec - 8.20.3 Stream End-point Type, Source or Sink (TSEP)) class StreamEndPointType(utils.OpenIntEnum):
AVDTP_TSEP_SRC = 0x00 '''TSEP (AVDTP spec - 8.20.3 Stream End-point Type, Source or Sink (TSEP)).'''
AVDTP_TSEP_SNK = 0x01 SRC = 0x00
SNK = 0x01
AVDTP_TSEP_NAMES = { AVDTP_TSEP_SRC = StreamEndPointType.SRC
AVDTP_TSEP_SRC: 'AVDTP_TSEP_SRC', AVDTP_TSEP_SNK = StreamEndPointType.SNK
AVDTP_TSEP_SNK: 'AVDTP_TSEP_SNK'
}
# Service Categories (AVDTP spec - Table 8.47: Service Category information element field values) class ServiceCategory(hci.SpecableEnum):
AVDTP_MEDIA_TRANSPORT_SERVICE_CATEGORY = 0x01 '''Service Categories (AVDTP spec - Table 8.47: Service Category information element field values).'''
AVDTP_REPORTING_SERVICE_CATEGORY = 0x02 MEDIA_TRANSPORT = 0x01
AVDTP_RECOVERY_SERVICE_CATEGORY = 0x03 REPORTING = 0x02
AVDTP_CONTENT_PROTECTION_SERVICE_CATEGORY = 0x04 RECOVERY = 0x03
AVDTP_HEADER_COMPRESSION_SERVICE_CATEGORY = 0x05 CONTENT_PROTECTION = 0x04
AVDTP_MULTIPLEXING_SERVICE_CATEGORY = 0x06 HEADER_COMPRESSION = 0x05
AVDTP_MEDIA_CODEC_SERVICE_CATEGORY = 0x07 MULTIPLEXING = 0x06
AVDTP_DELAY_REPORTING_SERVICE_CATEGORY = 0x08 MEDIA_CODEC = 0x07
DELAY_REPORTING = 0x08
AVDTP_SERVICE_CATEGORY_NAMES = { AVDTP_MEDIA_TRANSPORT_SERVICE_CATEGORY = ServiceCategory.MEDIA_TRANSPORT
AVDTP_MEDIA_TRANSPORT_SERVICE_CATEGORY: 'AVDTP_MEDIA_TRANSPORT_SERVICE_CATEGORY', AVDTP_REPORTING_SERVICE_CATEGORY = ServiceCategory.REPORTING
AVDTP_REPORTING_SERVICE_CATEGORY: 'AVDTP_REPORTING_SERVICE_CATEGORY', AVDTP_RECOVERY_SERVICE_CATEGORY = ServiceCategory.RECOVERY
AVDTP_RECOVERY_SERVICE_CATEGORY: 'AVDTP_RECOVERY_SERVICE_CATEGORY', AVDTP_CONTENT_PROTECTION_SERVICE_CATEGORY = ServiceCategory.CONTENT_PROTECTION
AVDTP_CONTENT_PROTECTION_SERVICE_CATEGORY: 'AVDTP_CONTENT_PROTECTION_SERVICE_CATEGORY', AVDTP_HEADER_COMPRESSION_SERVICE_CATEGORY = ServiceCategory.HEADER_COMPRESSION
AVDTP_HEADER_COMPRESSION_SERVICE_CATEGORY: 'AVDTP_HEADER_COMPRESSION_SERVICE_CATEGORY', AVDTP_MULTIPLEXING_SERVICE_CATEGORY = ServiceCategory.MULTIPLEXING
AVDTP_MULTIPLEXING_SERVICE_CATEGORY: 'AVDTP_MULTIPLEXING_SERVICE_CATEGORY', AVDTP_MEDIA_CODEC_SERVICE_CATEGORY = ServiceCategory.MEDIA_CODEC
AVDTP_MEDIA_CODEC_SERVICE_CATEGORY: 'AVDTP_MEDIA_CODEC_SERVICE_CATEGORY', AVDTP_DELAY_REPORTING_SERVICE_CATEGORY = ServiceCategory.DELAY_REPORTING
AVDTP_DELAY_REPORTING_SERVICE_CATEGORY: 'AVDTP_DELAY_REPORTING_SERVICE_CATEGORY'
}
# States (AVDTP spec - 9.1 State Definitions) class State(utils.OpenIntEnum):
AVDTP_IDLE_STATE = 0x00 '''States (AVDTP spec - 9.1 State Definitions)'''
AVDTP_CONFIGURED_STATE = 0x01 IDLE = 0x00
AVDTP_OPEN_STATE = 0x02 CONFIGURED = 0x01
AVDTP_STREAMING_STATE = 0x03 OPEN = 0x02
AVDTP_CLOSING_STATE = 0x04 STREAMING = 0x03
AVDTP_ABORTING_STATE = 0x05 CLOSING = 0x04
ABORTING = 0x05
AVDTP_STATE_NAMES = {
AVDTP_IDLE_STATE: 'AVDTP_IDLE_STATE',
AVDTP_CONFIGURED_STATE: 'AVDTP_CONFIGURED_STATE',
AVDTP_OPEN_STATE: 'AVDTP_OPEN_STATE',
AVDTP_STREAMING_STATE: 'AVDTP_STREAMING_STATE',
AVDTP_CLOSING_STATE: 'AVDTP_CLOSING_STATE',
AVDTP_ABORTING_STATE: 'AVDTP_ABORTING_STATE'
}
# fmt: on # fmt: on
# pylint: enable=line-too-long # pylint: enable=line-too-long
@@ -308,6 +297,7 @@ class MediaPacketPump:
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class MessageAssembler: class MessageAssembler:
message: Optional[bytes] message: Optional[bytes]
signal_identifier: SignalIdentifier
def __init__(self, callback: Callable[[int, Message], Any]) -> None: def __init__(self, callback: Callable[[int, Message], Any]) -> None:
self.callback = callback self.callback = callback
@@ -317,7 +307,7 @@ class MessageAssembler:
self.transaction_label = 0 self.transaction_label = 0
self.message = None self.message = None
self.message_type = Message.MessageType.COMMAND self.message_type = Message.MessageType.COMMAND
self.signal_identifier = 0 self.signal_identifier = SignalIdentifier(0)
self.number_of_signal_packets = 0 self.number_of_signal_packets = 0
self.packet_count = 0 self.packet_count = 0
@@ -346,7 +336,7 @@ class MessageAssembler:
self.reset() self.reset()
self.transaction_label = transaction_label self.transaction_label = transaction_label
self.signal_identifier = pdu[1] & 0x3F self.signal_identifier = SignalIdentifier(pdu[1] & 0x3F)
self.message_type = message_type self.message_type = message_type
if packet_type == Protocol.PacketType.SINGLE_PACKET: if packet_type == Protocol.PacketType.SINGLE_PACKET:
@@ -402,7 +392,9 @@ class MessageAssembler:
def on_message_complete(self) -> None: def on_message_complete(self) -> None:
message = Message.create( message = Message.create(
self.signal_identifier, self.message_type, self.message or b'' self.signal_identifier,
self.message_type,
self.message or b'',
) )
try: try:
self.callback(self.transaction_label, message) self.callback(self.transaction_label, message)
@@ -524,7 +516,7 @@ class EndPointInfo:
seid: int seid: int
in_use: int in_use: int
media_type: MediaType media_type: MediaType
tsep: int tsep: StreamEndPointType
@classmethod @classmethod
def from_bytes(cls, payload: bytes) -> EndPointInfo: def from_bytes(cls, payload: bytes) -> EndPointInfo:
@@ -532,7 +524,7 @@ class EndPointInfo:
seid=payload[0] >> 2, seid=payload[0] >> 2,
in_use=payload[0] >> 1 & 1, in_use=payload[0] >> 1 & 1,
media_type=MediaType(payload[1] >> 4), media_type=MediaType(payload[1] >> 4),
tsep=payload[1] >> 3 & 1, tsep=StreamEndPointType(payload[1] >> 3 & 1),
) )
def __bytes__(self) -> bytes: def __bytes__(self) -> bytes:
@@ -560,7 +552,7 @@ class Message:
subclasses: ClassVar[dict[int, dict[int, type[Message]]]] = {} subclasses: ClassVar[dict[int, dict[int, type[Message]]]] = {}
message_type: MessageType message_type: MessageType
signal_identifier: int signal_identifier: SignalIdentifier
_payload: Optional[bytes] = None _payload: Optional[bytes] = None
fields: ClassVar[hci.Fields] = () fields: ClassVar[hci.Fields] = ()
@@ -588,7 +580,10 @@ class Message:
# type # type
@classmethod @classmethod
def create( def create(
cls, signal_identifier: int, message_type: MessageType, payload: bytes cls,
signal_identifier: SignalIdentifier,
message_type: MessageType,
payload: bytes,
) -> Message: ) -> Message:
instance: Message instance: Message
# Look for a registered subclass # Look for a registered subclass
@@ -604,7 +599,7 @@ class Message:
# Instantiate the appropriate class based on the message type # Instantiate the appropriate class based on the message type
if message_type == Message.MessageType.RESPONSE_REJECT: if message_type == Message.MessageType.RESPONSE_REJECT:
# Assume a simple reject message # Assume a simple reject message
instance = Simple_Reject(payload[0]) instance = Simple_Reject(ErrorCode(payload[0]))
else: else:
instance = Message() instance = Message()
instance.payload = payload instance.payload = payload
@@ -614,8 +609,7 @@ class Message:
def to_string(self, details: Union[str, Iterable[str]]) -> str: def to_string(self, details: Union[str, Iterable[str]]) -> str:
base = color( base = color(
f'{name_or_number(AVDTP_SIGNAL_NAMES, self.signal_identifier)}_' f'{self.signal_identifier.name}_{self.message_type.name}',
f'{self.message_type.name}',
'yellow', 'yellow',
) )
@@ -659,10 +653,10 @@ class Simple_Reject(Message):
message_type = Message.MessageType.RESPONSE_REJECT message_type = Message.MessageType.RESPONSE_REJECT
error_code: int = field(metadata=hci.metadata(1)) error_code: ErrorCode = field(metadata=ErrorCode.type_metadata(1))
def __str__(self) -> str: def __str__(self) -> str:
details = [f'error_code: {name_or_number(AVDTP_ERROR_NAMES, self.error_code)}'] details = [f'error_code: {self.error_code.name}']
return self.to_string(details) return self.to_string(details)
@@ -724,7 +718,7 @@ class Discover_Response(Message):
f'ACP SEID: {endpoint.seid}', f'ACP SEID: {endpoint.seid}',
f' in_use: {endpoint.in_use}', f' in_use: {endpoint.in_use}',
f' media_type: {endpoint.media_type.name}', f' media_type: {endpoint.media_type.name}',
f' tsep: {name_or_number(AVDTP_TSEP_NAMES, endpoint.tsep)}', f' tsep: {endpoint.tsep.name}',
] ]
) )
return self.to_string(details) return self.to_string(details)
@@ -857,19 +851,17 @@ class Set_Configuration_Reject(Message):
signal_identifier = AVDTP_SET_CONFIGURATION signal_identifier = AVDTP_SET_CONFIGURATION
message_type = Message.MessageType.RESPONSE_REJECT message_type = Message.MessageType.RESPONSE_REJECT
service_category: int = field(metadata=hci.metadata(1), default=0) service_category: ServiceCategory = field(
error_code: int = field(metadata=hci.metadata(1), default=0) metadata=ServiceCategory.type_metadata(1), default=ServiceCategory(0)
)
error_code: ErrorCode = field(
metadata=ErrorCode.type_metadata(1), default=ErrorCode(0)
)
def __str__(self) -> str: def __str__(self) -> str:
details = [ details = [
( (f'service_category: {self.service_category.name}'),
'service_category: ' (f'error_code: {self.error_code.name}'),
f'{name_or_number(AVDTP_SERVICE_CATEGORY_NAMES, self.service_category)}'
),
(
'error_code: '
f'{name_or_number(AVDTP_ERROR_NAMES, self.error_code)}'
),
] ]
return self.to_string(details) return self.to_string(details)
@@ -1052,12 +1044,12 @@ class Start_Reject(Message):
message_type = Message.MessageType.RESPONSE_REJECT message_type = Message.MessageType.RESPONSE_REJECT
acp_seid: int = field(metadata=Message.SEID_METADATA) acp_seid: int = field(metadata=Message.SEID_METADATA)
error_code: int = field(metadata=hci.metadata(1)) error_code: ErrorCode = field(metadata=ErrorCode.type_metadata(1))
def __str__(self) -> str: def __str__(self) -> str:
details = [ details = [
f'acp_seid: {self.acp_seid}', f'acp_seid: {self.acp_seid}',
f'error_code: {name_or_number(AVDTP_ERROR_NAMES, self.error_code)}', f'error_code: {self.error_code.name}',
] ]
return self.to_string(details) return self.to_string(details)
@@ -1210,7 +1202,7 @@ class General_Reject(Message):
See Bluetooth AVDTP spec - 8.18 General Reject See Bluetooth AVDTP spec - 8.18 General Reject
''' '''
signal_identifier = 0 signal_identifier = SignalIdentifier(0)
message_type = Message.MessageType.GENERAL_REJECT message_type = Message.MessageType.GENERAL_REJECT
def to_string(self, details): def to_string(self, details):
@@ -1276,6 +1268,7 @@ class Protocol(utils.EventEmitter):
streams: dict[int, Stream] streams: dict[int, Stream]
transaction_results: list[Optional[asyncio.Future[Message]]] transaction_results: list[Optional[asyncio.Future[Message]]]
channel_connector: Callable[[], Awaitable[l2cap.ClassicChannel]] channel_connector: Callable[[], Awaitable[l2cap.ClassicChannel]]
channel_acceptor: Optional[Stream]
EVENT_OPEN = "open" EVENT_OPEN = "open"
EVENT_CLOSE = "close" EVENT_CLOSE = "close"
@@ -1286,10 +1279,6 @@ class Protocol(utils.EventEmitter):
CONTINUE_PACKET = 2 CONTINUE_PACKET = 2
END_PACKET = 3 END_PACKET = 3
@staticmethod
def packet_type_name(packet_type):
return name_or_number(Protocol.PACKET_TYPE_NAMES, packet_type)
@staticmethod @staticmethod
async def connect( async def connect(
connection: device.Connection, version: tuple[int, int] = (1, 3) connection: device.Connection, version: tuple[int, int] = (1, 3)
@@ -1462,11 +1451,7 @@ class Protocol(utils.EventEmitter):
if message.message_type == Message.MessageType.COMMAND: if message.message_type == Message.MessageType.COMMAND:
# Command # Command
signal_name = ( signal_name = message.signal_identifier.name.lower()
AVDTP_SIGNAL_NAMES.get(message.signal_identifier, "")
.replace("AVDTP_", "")
.lower()
)
handler_name = f'on_{signal_name}_command' handler_name = f'on_{signal_name}_command'
handler = getattr(self, handler_name, None) handler = getattr(self, handler_name, None)
if handler: if handler:
@@ -1649,11 +1634,11 @@ class Protocol(utils.EventEmitter):
) -> Optional[Message]: ) -> Optional[Message]:
endpoint = self.get_local_endpoint_by_seid(command.acp_seid) endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
if endpoint is None: if endpoint is None:
return Set_Configuration_Reject(AVDTP_BAD_ACP_SEID_ERROR) return Set_Configuration_Reject(error_code=AVDTP_BAD_ACP_SEID_ERROR)
# Check that the local endpoint isn't in use # Check that the local endpoint isn't in use
if endpoint.in_use: if endpoint.in_use:
return Set_Configuration_Reject(AVDTP_SEP_IN_USE_ERROR) return Set_Configuration_Reject(error_code=AVDTP_SEP_IN_USE_ERROR)
# Create a stream object for the pair of endpoints # Create a stream object for the pair of endpoints
stream = Stream(self, endpoint, StreamEndPointProxy(self, command.int_seid)) stream = Stream(self, endpoint, StreamEndPointProxy(self, command.int_seid))
@@ -1676,9 +1661,9 @@ class Protocol(utils.EventEmitter):
def on_reconfigure_command(self, command: Reconfigure_Command) -> Optional[Message]: def on_reconfigure_command(self, command: Reconfigure_Command) -> Optional[Message]:
endpoint = self.get_local_endpoint_by_seid(command.acp_seid) endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
if endpoint is None: if endpoint is None:
return Reconfigure_Reject(0, AVDTP_BAD_ACP_SEID_ERROR) return Reconfigure_Reject(error_code=AVDTP_BAD_ACP_SEID_ERROR)
if endpoint.stream is None: if endpoint.stream is None:
return Reconfigure_Reject(0, AVDTP_BAD_STATE_ERROR) return Reconfigure_Reject(error_code=AVDTP_BAD_STATE_ERROR)
result = endpoint.stream.on_reconfigure_command(command.capabilities) result = endpoint.stream.on_reconfigure_command(command.capabilities)
return result or Reconfigure_Response() return result or Reconfigure_Response()
@@ -1842,12 +1827,8 @@ class Stream:
rtp_channel: Optional[l2cap.ClassicChannel] rtp_channel: Optional[l2cap.ClassicChannel]
@staticmethod def change_state(self, state: State) -> None:
def state_name(state: int) -> str: logger.debug(f'{self} state change -> {color(state.name, "cyan")}')
return name_or_number(AVDTP_STATE_NAMES, state)
def change_state(self, state: int) -> None:
logger.debug(f'{self} state change -> {color(self.state_name(state), "cyan")}')
self.state = state self.state = state
def send_media_packet(self, packet: MediaPacket) -> None: def send_media_packet(self, packet: MediaPacket) -> None:
@@ -1855,22 +1836,22 @@ class Stream:
self.rtp_channel.send_pdu(bytes(packet)) self.rtp_channel.send_pdu(bytes(packet))
async def configure(self) -> None: async def configure(self) -> None:
if self.state != AVDTP_IDLE_STATE: if self.state != State.IDLE:
raise InvalidStateError('current state is not IDLE') raise InvalidStateError('current state is not IDLE')
await self.remote_endpoint.set_configuration( await self.remote_endpoint.set_configuration(
self.local_endpoint.seid, self.local_endpoint.configuration self.local_endpoint.seid, self.local_endpoint.configuration
) )
self.change_state(AVDTP_CONFIGURED_STATE) self.change_state(State.CONFIGURED)
async def open(self) -> None: async def open(self) -> None:
if self.state != AVDTP_CONFIGURED_STATE: if self.state != State.CONFIGURED:
raise InvalidStateError('current state is not CONFIGURED') raise InvalidStateError('current state is not CONFIGURED')
logger.debug('opening remote endpoint') logger.debug('opening remote endpoint')
await self.remote_endpoint.open() await self.remote_endpoint.open()
self.change_state(AVDTP_OPEN_STATE) self.change_state(State.OPEN)
# Create a channel for RTP packets # Create a channel for RTP packets
self.rtp_channel = ( self.rtp_channel = (
@@ -1882,10 +1863,10 @@ class Stream:
async def start(self) -> None: async def start(self) -> None:
"""[Source] Start streaming.""" """[Source] Start streaming."""
# Auto-open if needed # Auto-open if needed
if self.state == AVDTP_CONFIGURED_STATE: if self.state == State.CONFIGURED:
await self.open() await self.open()
if self.state != AVDTP_OPEN_STATE: if self.state != State.OPEN:
raise InvalidStateError('current state is not OPEN') raise InvalidStateError('current state is not OPEN')
logger.debug('starting remote endpoint') logger.debug('starting remote endpoint')
@@ -1894,11 +1875,11 @@ class Stream:
logger.debug('starting local endpoint') logger.debug('starting local endpoint')
await self.local_endpoint.start() await self.local_endpoint.start()
self.change_state(AVDTP_STREAMING_STATE) self.change_state(State.STREAMING)
async def stop(self) -> None: async def stop(self) -> None:
"""[Source] Stop streaming and transit to OPEN state.""" """[Source] Stop streaming and transit to OPEN state."""
if self.state != AVDTP_STREAMING_STATE: if self.state != State.STREAMING:
raise InvalidStateError('current state is not STREAMING') raise InvalidStateError('current state is not STREAMING')
logger.debug('stopping local endpoint') logger.debug('stopping local endpoint')
@@ -1907,11 +1888,11 @@ class Stream:
logger.debug('stopping remote endpoint') logger.debug('stopping remote endpoint')
await self.remote_endpoint.stop() await self.remote_endpoint.stop()
self.change_state(AVDTP_OPEN_STATE) self.change_state(State.OPEN)
async def close(self) -> None: async def close(self) -> None:
"""[Source] Close channel and transit to IDLE state.""" """[Source] Close channel and transit to IDLE state."""
if self.state not in (AVDTP_OPEN_STATE, AVDTP_STREAMING_STATE): if self.state not in (State.OPEN, State.STREAMING):
raise InvalidStateError('current state is not OPEN or STREAMING') raise InvalidStateError('current state is not OPEN or STREAMING')
logger.debug('closing local endpoint') logger.debug('closing local endpoint')
@@ -1921,7 +1902,7 @@ class Stream:
await self.remote_endpoint.close() await self.remote_endpoint.close()
# Release any channels we may have created # Release any channels we may have created
self.change_state(AVDTP_CLOSING_STATE) self.change_state(State.CLOSING)
if self.rtp_channel: if self.rtp_channel:
await self.rtp_channel.disconnect() await self.rtp_channel.disconnect()
self.rtp_channel = None self.rtp_channel = None
@@ -1929,32 +1910,36 @@ class Stream:
# Release the endpoint # Release the endpoint
self.local_endpoint.in_use = 0 self.local_endpoint.in_use = 0
self.change_state(AVDTP_IDLE_STATE) self.change_state(State.IDLE)
def on_set_configuration_command(self, configuration): def on_set_configuration_command(
if self.state != AVDTP_IDLE_STATE: self, configuration: Iterable[ServiceCapabilities]
return Set_Configuration_Reject(AVDTP_BAD_STATE_ERROR) ) -> Optional[Message]:
if self.state != State.IDLE:
return Set_Configuration_Reject(error_code=AVDTP_BAD_STATE_ERROR)
result = self.local_endpoint.on_set_configuration_command(configuration) result = self.local_endpoint.on_set_configuration_command(configuration)
if result is not None: if result is not None:
return result return result
self.change_state(AVDTP_CONFIGURED_STATE) self.change_state(State.CONFIGURED)
return None return None
def on_get_configuration_command(self): def on_get_configuration_command(self) -> Optional[Message]:
if self.state not in ( if self.state not in (
AVDTP_CONFIGURED_STATE, State.CONFIGURED,
AVDTP_OPEN_STATE, State.OPEN,
AVDTP_STREAMING_STATE, State.STREAMING,
): ):
return Get_Configuration_Reject(AVDTP_BAD_STATE_ERROR) return Get_Configuration_Reject(error_code=AVDTP_BAD_STATE_ERROR)
return self.local_endpoint.on_get_configuration_command() return self.local_endpoint.on_get_configuration_command()
def on_reconfigure_command(self, configuration): def on_reconfigure_command(
if self.state != AVDTP_OPEN_STATE: self, configuration: Iterable[ServiceCapabilities]
return Reconfigure_Reject(AVDTP_BAD_STATE_ERROR) ) -> Optional[Message]:
if self.state != State.OPEN:
return Reconfigure_Reject(error_code=AVDTP_BAD_STATE_ERROR)
result = self.local_endpoint.on_reconfigure_command(configuration) result = self.local_endpoint.on_reconfigure_command(configuration)
if result is not None: if result is not None:
@@ -1962,8 +1947,8 @@ class Stream:
return None return None
def on_open_command(self): def on_open_command(self) -> Optional[Message]:
if self.state != AVDTP_CONFIGURED_STATE: if self.state != State.CONFIGURED:
return Open_Reject(AVDTP_BAD_STATE_ERROR) return Open_Reject(AVDTP_BAD_STATE_ERROR)
result = self.local_endpoint.on_open_command() result = self.local_endpoint.on_open_command()
@@ -1973,11 +1958,11 @@ class Stream:
# Register to accept the next channel # Register to accept the next channel
self.protocol.channel_acceptor = self self.protocol.channel_acceptor = self
self.change_state(AVDTP_OPEN_STATE) self.change_state(State.OPEN)
return None return None
def on_start_command(self): def on_start_command(self) -> Optional[Message]:
if self.state != AVDTP_OPEN_STATE: if self.state != State.OPEN:
return Open_Reject(AVDTP_BAD_STATE_ERROR) return Open_Reject(AVDTP_BAD_STATE_ERROR)
# Check that we have an RTP channel # Check that we have an RTP channel
@@ -1989,46 +1974,47 @@ class Stream:
if result is not None: if result is not None:
return result return result
self.change_state(AVDTP_STREAMING_STATE) self.change_state(State.STREAMING)
return None return None
def on_suspend_command(self): def on_suspend_command(self) -> Optional[Message]:
if self.state != AVDTP_STREAMING_STATE: if self.state != State.STREAMING:
return Open_Reject(AVDTP_BAD_STATE_ERROR) return Open_Reject(AVDTP_BAD_STATE_ERROR)
result = self.local_endpoint.on_suspend_command() result = self.local_endpoint.on_suspend_command()
if result is not None: if result is not None:
return result return result
self.change_state(AVDTP_OPEN_STATE) self.change_state(State.OPEN)
return None return None
def on_close_command(self): def on_close_command(self) -> Optional[Message]:
if self.state not in (AVDTP_OPEN_STATE, AVDTP_STREAMING_STATE): if self.state not in (State.OPEN, State.STREAMING):
return Open_Reject(AVDTP_BAD_STATE_ERROR) return Open_Reject(AVDTP_BAD_STATE_ERROR)
result = self.local_endpoint.on_close_command() result = self.local_endpoint.on_close_command()
if result is not None: if result is not None:
return result return result
self.change_state(AVDTP_CLOSING_STATE) self.change_state(State.CLOSING)
if self.rtp_channel is None: if self.rtp_channel is None:
# No channel to release, we're done # No channel to release, we're done
self.change_state(AVDTP_IDLE_STATE) self.change_state(State.IDLE)
else: else:
# TODO: set a timer as we wait for the RTP channel to be closed # TODO: set a timer as we wait for the RTP channel to be closed
pass pass
return None return None
def on_abort_command(self): def on_abort_command(self) -> Optional[Message]:
if self.rtp_channel is None: if self.rtp_channel is None:
# No need to wait # No need to wait
self.change_state(AVDTP_IDLE_STATE) self.change_state(State.IDLE)
else: else:
# Wait for the RTP channel to be closed # Wait for the RTP channel to be closed
self.change_state(AVDTP_ABORTING_STATE) self.change_state(State.ABORTING)
return None
def on_l2cap_connection(self, channel: l2cap.ClassicChannel) -> None: def on_l2cap_connection(self, channel: l2cap.ClassicChannel) -> None:
logger.debug(color('<<< stream channel connected', 'magenta')) logger.debug(color('<<< stream channel connected', 'magenta'))
@@ -2049,8 +2035,8 @@ class Stream:
self.local_endpoint.in_use = 0 self.local_endpoint.in_use = 0
self.rtp_channel = None self.rtp_channel = None
if self.state in (AVDTP_CLOSING_STATE, AVDTP_ABORTING_STATE): if self.state in (State.CLOSING, State.ABORTING):
self.change_state(AVDTP_IDLE_STATE) self.change_state(State.IDLE)
else: else:
logger.warning('unexpected channel close while not CLOSING or ABORTING') logger.warning('unexpected channel close while not CLOSING or ABORTING')
@@ -2068,7 +2054,7 @@ class Stream:
self.local_endpoint = local_endpoint self.local_endpoint = local_endpoint
self.remote_endpoint = remote_endpoint self.remote_endpoint = remote_endpoint
self.rtp_channel = None self.rtp_channel = None
self.state = AVDTP_IDLE_STATE self.state = State.IDLE
local_endpoint.stream = self local_endpoint.stream = self
local_endpoint.in_use = 1 local_endpoint.in_use = 1
@@ -2076,7 +2062,7 @@ class Stream:
def __str__(self) -> str: def __str__(self) -> str:
return ( return (
f'Stream({self.local_endpoint.seid} -> ' f'Stream({self.local_endpoint.seid} -> '
f'{self.remote_endpoint.seid} {self.state_name(self.state)})' f'{self.remote_endpoint.seid} {self.state.name})'
) )
@@ -2085,7 +2071,7 @@ class Stream:
class StreamEndPoint: class StreamEndPoint:
seid: int seid: int
media_type: MediaType media_type: MediaType
tsep: int tsep: StreamEndPointType
in_use: int in_use: int
capabilities: Iterable[ServiceCapabilities] capabilities: Iterable[ServiceCapabilities]
@@ -2124,7 +2110,7 @@ class DiscoveredStreamEndPoint(StreamEndPoint, StreamEndPointProxy):
protocol: Protocol, protocol: Protocol,
seid: int, seid: int,
media_type: MediaType, media_type: MediaType,
tsep: int, tsep: StreamEndPointType,
in_use: int, in_use: int,
capabilities: Iterable[ServiceCapabilities], capabilities: Iterable[ServiceCapabilities],
) -> None: ) -> None:
@@ -2154,7 +2140,7 @@ class LocalStreamEndPoint(StreamEndPoint, utils.EventEmitter):
protocol: Protocol, protocol: Protocol,
seid: int, seid: int,
media_type: MediaType, media_type: MediaType,
tsep: int, tsep: StreamEndPointType,
capabilities: Iterable[ServiceCapabilities], capabilities: Iterable[ServiceCapabilities],
configuration: Optional[Iterable[ServiceCapabilities]] = None, configuration: Optional[Iterable[ServiceCapabilities]] = None,
): ):
@@ -2173,10 +2159,15 @@ class LocalStreamEndPoint(StreamEndPoint, utils.EventEmitter):
async def close(self) -> None: async def close(self) -> None:
"""[Source Only] Handles when receiving close command.""" """[Source Only] Handles when receiving close command."""
def on_reconfigure_command(self, command) -> Optional[Message]: def on_reconfigure_command(
self, command: Iterable[ServiceCapabilities]
) -> Optional[Message]:
del command # unused.
return None return None
def on_set_configuration_command(self, configuration) -> Optional[Message]: def on_set_configuration_command(
self, configuration: Iterable[ServiceCapabilities]
) -> Optional[Message]:
logger.debug( logger.debug(
'<<< received configuration: ' '<<< received configuration: '
f'{",".join([str(capability) for capability in configuration])}' f'{",".join([str(capability) for capability in configuration])}'
@@ -2232,13 +2223,13 @@ class LocalSource(LocalStreamEndPoint):
protocol: Protocol, protocol: Protocol,
seid: int, seid: int,
codec_capabilities: MediaCodecCapabilities, codec_capabilities: MediaCodecCapabilities,
other_capabilitiles: Iterable[ServiceCapabilities], other_capabilities: Iterable[ServiceCapabilities],
packet_pump: MediaPacketPump, packet_pump: MediaPacketPump,
) -> None: ) -> None:
capabilities = [ capabilities = [
ServiceCapabilities(AVDTP_MEDIA_TRANSPORT_SERVICE_CATEGORY), ServiceCapabilities(AVDTP_MEDIA_TRANSPORT_SERVICE_CATEGORY),
codec_capabilities, codec_capabilities,
] + list(other_capabilitiles) ] + list(other_capabilities)
super().__init__( super().__init__(
protocol, protocol,
seid, seid,
@@ -2249,23 +2240,29 @@ class LocalSource(LocalStreamEndPoint):
) )
self.packet_pump = packet_pump self.packet_pump = packet_pump
@override
async def start(self) -> None: async def start(self) -> None:
if self.packet_pump and self.stream and self.stream.rtp_channel: if self.packet_pump and self.stream and self.stream.rtp_channel:
return await self.packet_pump.start(self.stream.rtp_channel) return await self.packet_pump.start(self.stream.rtp_channel)
self.emit(self.EVENT_START) self.emit(self.EVENT_START)
@override
async def stop(self) -> None: async def stop(self) -> None:
if self.packet_pump: if self.packet_pump:
return await self.packet_pump.stop() return await self.packet_pump.stop()
self.emit(self.EVENT_STOP) self.emit(self.EVENT_STOP)
def on_start_command(self): @override
def on_start_command(self) -> Optional[Message]:
asyncio.create_task(self.start()) asyncio.create_task(self.start())
return None
def on_suspend_command(self): @override
def on_suspend_command(self) -> Optional[Message]:
asyncio.create_task(self.stop()) asyncio.create_task(self.stop())
return None
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@@ -2285,16 +2282,20 @@ class LocalSink(LocalStreamEndPoint):
capabilities, capabilities,
) )
def on_rtp_channel_open(self): def on_rtp_channel_open(self) -> None:
logger.debug(color('<<< RTP channel open', 'magenta')) logger.debug(color('<<< RTP channel open', 'magenta'))
if not self.stream:
raise InvalidStateError('Stream is None')
if not self.stream.rtp_channel:
raise InvalidStateError('RTP channel is None')
self.stream.rtp_channel.sink = self.on_avdtp_packet self.stream.rtp_channel.sink = self.on_avdtp_packet
super().on_rtp_channel_open() super().on_rtp_channel_open()
def on_rtp_channel_close(self): def on_rtp_channel_close(self) -> None:
logger.debug(color('<<< RTP channel close', 'magenta')) logger.debug(color('<<< RTP channel close', 'magenta'))
super().on_rtp_channel_close() super().on_rtp_channel_close()
def on_avdtp_packet(self, packet): def on_avdtp_packet(self, packet: bytes) -> None:
rtp_packet = MediaPacket.from_bytes(packet) rtp_packet = MediaPacket.from_bytes(packet)
logger.debug( logger.debug(
f'{color("<<< RTP Packet:", "green")} ' f'{color("<<< RTP Packet:", "green")} '

View File

@@ -23,17 +23,7 @@ from typing import Awaitable
import pytest import pytest
from bumble import a2dp from bumble import a2dp, avdtp
from bumble.avdtp import (
AVDTP_AUDIO_MEDIA_TYPE,
AVDTP_IDLE_STATE,
AVDTP_STREAMING_STATE,
AVDTP_TSEP_SNK,
Listener,
MediaCodecCapabilities,
MediaPacketPump,
Protocol,
)
from bumble.controller import Controller from bumble.controller import Controller
from bumble.core import PhysicalTransport from bumble.core import PhysicalTransport
from bumble.device import Device from bumble.device import Device
@@ -134,8 +124,8 @@ async def test_self_connection():
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
def source_codec_capabilities(): def source_codec_capabilities():
return MediaCodecCapabilities( return avdtp.MediaCodecCapabilities(
media_type=AVDTP_AUDIO_MEDIA_TYPE, media_type=avdtp.MediaType.AUDIO,
media_codec_type=a2dp.CodecType.SBC, media_codec_type=a2dp.CodecType.SBC,
media_codec_information=a2dp.SbcMediaCodecInformation( media_codec_information=a2dp.SbcMediaCodecInformation(
sampling_frequency=a2dp.SbcMediaCodecInformation.SamplingFrequency.SF_44100, sampling_frequency=a2dp.SbcMediaCodecInformation.SamplingFrequency.SF_44100,
@@ -151,8 +141,8 @@ def source_codec_capabilities():
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
def sink_codec_capabilities(): def sink_codec_capabilities():
return MediaCodecCapabilities( return avdtp.MediaCodecCapabilities(
media_type=AVDTP_AUDIO_MEDIA_TYPE, media_type=avdtp.MediaType.AUDIO,
media_codec_type=a2dp.CodecType.SBC, media_codec_type=a2dp.CodecType.SBC,
media_codec_information=a2dp.SbcMediaCodecInformation( media_codec_information=a2dp.SbcMediaCodecInformation(
sampling_frequency=a2dp.SbcMediaCodecInformation.SamplingFrequency.SF_48000 sampling_frequency=a2dp.SbcMediaCodecInformation.SamplingFrequency.SF_48000
@@ -200,7 +190,7 @@ async def test_source_sink_1():
sink.on('rtp_packet', on_rtp_packet) sink.on('rtp_packet', on_rtp_packet)
# Create a listener to wait for AVDTP connections # Create a listener to wait for AVDTP connections
listener = Listener.for_device(two_devices.devices[1]) listener = avdtp.Listener.for_device(two_devices.devices[1])
listener.on('connection', on_avdtp_connection) listener.on('connection', on_avdtp_connection)
async def make_connection(): async def make_connection():
@@ -213,13 +203,13 @@ async def test_source_sink_1():
return connections[0] return connections[0]
connection = await make_connection() connection = await make_connection()
client = await Protocol.connect(connection) client = await avdtp.Protocol.connect(connection)
endpoints = await client.discover_remote_endpoints() endpoints = await client.discover_remote_endpoints()
assert len(endpoints) == 1 assert len(endpoints) == 1
remote_sink = list(endpoints)[0] remote_sink = list(endpoints)[0]
assert remote_sink.in_use == 0 assert remote_sink.in_use == 0
assert remote_sink.media_type == AVDTP_AUDIO_MEDIA_TYPE assert remote_sink.media_type == avdtp.MediaType.AUDIO
assert remote_sink.tsep == AVDTP_TSEP_SNK assert remote_sink.tsep == avdtp.StreamEndPointType.SNK
async def generate_packets(packet_count): async def generate_packets(packet_count):
sequence_number = 0 sequence_number = 0
@@ -238,24 +228,24 @@ async def test_source_sink_1():
rtp_packets_fully_received = asyncio.get_running_loop().create_future() rtp_packets_fully_received = asyncio.get_running_loop().create_future()
rtp_packets_expected = 3 rtp_packets_expected = 3
rtp_packets = [] rtp_packets = []
pump = MediaPacketPump(generate_packets(3)) pump = avdtp.MediaPacketPump(generate_packets(3))
source = client.add_source(source_codec_capabilities(), pump) source = client.add_source(source_codec_capabilities(), pump)
stream = await client.create_stream(source, remote_sink) stream = await client.create_stream(source, remote_sink)
await stream.start() await stream.start()
assert stream.state == AVDTP_STREAMING_STATE assert stream.state == avdtp.State.STREAMING
assert stream.local_endpoint.in_use == 1 assert stream.local_endpoint.in_use == 1
assert stream.rtp_channel is not None assert stream.rtp_channel is not None
assert sink.in_use == 1 assert sink.in_use == 1
assert sink.stream is not None assert sink.stream is not None
assert sink.stream.state == AVDTP_STREAMING_STATE assert sink.stream.state == avdtp.State.STREAMING
await rtp_packets_fully_received await rtp_packets_fully_received
await stream.close() await stream.close()
assert stream.rtp_channel is None assert stream.rtp_channel is None
assert source.in_use == 0 assert source.in_use == 0
assert source.stream.state == AVDTP_IDLE_STATE assert source.stream.state == avdtp.State.IDLE
assert sink.in_use == 0 assert sink.in_use == 0
assert sink.stream.state == AVDTP_IDLE_STATE assert sink.stream.state == avdtp.State.IDLE
# Send packets manually # Send packets manually
rtp_packets_fully_received = asyncio.get_running_loop().create_future() rtp_packets_fully_received = asyncio.get_running_loop().create_future()
@@ -267,12 +257,12 @@ async def test_source_sink_1():
source = client.add_source(source_codec_capabilities(), None) source = client.add_source(source_codec_capabilities(), None)
stream = await client.create_stream(source, remote_sink) stream = await client.create_stream(source, remote_sink)
await stream.start() await stream.start()
assert stream.state == AVDTP_STREAMING_STATE assert stream.state == avdtp.State.STREAMING
assert stream.local_endpoint.in_use == 1 assert stream.local_endpoint.in_use == 1
assert stream.rtp_channel is not None assert stream.rtp_channel is not None
assert sink.in_use == 1 assert sink.in_use == 1
assert sink.stream is not None assert sink.stream is not None
assert sink.stream.state == AVDTP_STREAMING_STATE assert sink.stream.state == avdtp.State.STREAMING
stream.send_media_packet(source_packets[0]) stream.send_media_packet(source_packets[0])
stream.send_media_packet(source_packets[1]) stream.send_media_packet(source_packets[1])
@@ -282,9 +272,9 @@ async def test_source_sink_1():
assert stream.rtp_channel is None assert stream.rtp_channel is None
assert len(rtp_packets) == 3 assert len(rtp_packets) == 3
assert source.in_use == 0 assert source.in_use == 0
assert source.stream.state == AVDTP_IDLE_STATE assert source.stream.state == avdtp.State.IDLE
assert sink.in_use == 0 assert sink.in_use == 0
assert sink.stream.state == AVDTP_IDLE_STATE assert sink.stream.state == avdtp.State.IDLE
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------

View File

@@ -30,7 +30,10 @@ from bumble.rtp import MediaPacket
avdtp.Discover_Response( avdtp.Discover_Response(
endpoints=[ endpoints=[
avdtp.EndPointInfo( avdtp.EndPointInfo(
seid=1, in_use=1, media_type=avdtp.MediaType.AUDIO, tsep=1 seid=1,
in_use=1,
media_type=avdtp.MediaType.AUDIO,
tsep=avdtp.StreamEndPointType.SNK,
) )
] ]
), ),