mirror of
https://github.com/google/bumble.git
synced 2026-04-18 00:45:32 +00:00
419
bumble/avdtp.py
419
bumble/avdtp.py
@@ -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")} '
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -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,
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user