-
Notifications
You must be signed in to change notification settings - Fork 76
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
a2dp player #571
a2dp player #571
Conversation
bumble/a2dp.py
Outdated
OPUS_VENDOR_ID = 0x000000E0 | ||
OPUS_CODEC_ID = 0x0001 | ||
OPUS_MAX_FRAMES_IN_RTP_PAYLOAD = 15 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think these could be a part of OpusMediaCodecInformation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
bumble/a2dp.py
Outdated
class ChannelMode(enum.IntEnum): | ||
MONO = 0 | ||
STEREO = 1 | ||
DUAL_MONO = 2 | ||
|
||
CHANNEL_MODE_BITS = { | ||
ChannelMode.MONO: 1 << 0, | ||
ChannelMode.STEREO: 1 << 1, | ||
ChannelMode.DUAL_MONO: 1 << 2, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think these constants should be flag instead of enum, so that we don't need additional maps and from_discrete_values
to build and serialize codec informations (also applicable to SBC and AAC).
class OpusCodecInformation:
"""OPUS codec information."""
class ChannelMode(enum.IntFlag):
MONO = 0x01
STEREO = 0x02
DUAL_MONO = 0x04
class FrameSize(enum.IntFlag):
SIZE_10_MILLISECONDS = 0x08
SIZE_20_MILLISECONDS = 0x10
class SamplingRate(enum.IntFlag):
RATE_48000 = 0x80
sample_rate: SamplingRate
channel_mode: ChannelMode
frame_size: FrameSize
VENDOR_ID: ClassVar[int] = 0xE0
CODEC_ID: ClassVar[int] = 0x01
def __bytes__(self) -> bytes:
return struct.pack(
'<IHB',
self.VENDOR_ID,
self.CODEC_ID,
self.sample_rate | self.channel_mode | self.frame_size,
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think that's much better. I've done that for all 3 types now.
Initially, I thought it would be convenient to treat some fields like sampling frequencies as ints instead of enums, but that just complicates everything for no good reason. Using IntFlag
for everything is the right approach.
bumble/a2dp.py
Outdated
# pylint: disable=import-outside-toplevel | ||
from .avdtp import MediaPacket # Import here to avoid a circular reference |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we move MediaPacket to a third module so that we can avoid inline imports?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. MediaPacket
is not in rtp.py
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one is left
if isinstance( | ||
codec_capabilities.media_codec_information, | ||
VendorSpecificMediaCodecInformation, | ||
): | ||
if ( | ||
codec_capabilities.media_codec_information.vendor_id | ||
== vendor_id | ||
and codec_capabilities.media_codec_information.codec_id | ||
== codec_id | ||
): | ||
has_codec = True | ||
else: | ||
has_codec = True |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: If there isn't additional works here
if isinstance( | |
codec_capabilities.media_codec_information, | |
VendorSpecificMediaCodecInformation, | |
): | |
if ( | |
codec_capabilities.media_codec_information.vendor_id | |
== vendor_id | |
and codec_capabilities.media_codec_information.codec_id | |
== codec_id | |
): | |
has_codec = True | |
else: | |
has_codec = True | |
has_codec = (isinstance( | |
codec_capabilities.media_codec_information, | |
VendorSpecificMediaCodecInformation, | |
) and codec_capabilities.media_codec_information.vendor_id == vendor_id | |
and codec_capabilities.media_codec_information.codec_id == codec_id) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's not 100% the same: if we encounter a capabilities for a standard (non-vendor) codec, we want has_codec
to be True
.
bumble/a2dp.py
Outdated
(granule_position,) = struct.unpack_from("<Q", header, 6) | ||
(bitstream_serial_number,) = struct.unpack_from("<I", header, 14) | ||
(page_sequence_number,) = struct.unpack_from("<I", header, 18) | ||
(crc_checksum,) = struct.unpack_from("<I", header, 22) | ||
page_segments = header[26] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(granule_position,) = struct.unpack_from("<Q", header, 6) | |
(bitstream_serial_number,) = struct.unpack_from("<I", header, 14) | |
(page_sequence_number,) = struct.unpack_from("<I", header, 18) | |
(crc_checksum,) = struct.unpack_from("<I", header, 22) | |
page_segments = header[26] | |
(granule_position, bitstream_serial_number, page_sequence_number, crc_checksum, page_segments) = struct.unpack_from("<QIIIB", header, 6) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
@@ -51,6 +51,7 @@ | |||
A2DP_MPEG_2_4_AAC_CODEC_TYPE, | |||
A2DP_NON_A2DP_CODEC_TYPE, | |||
A2DP_SBC_CODEC_TYPE, | |||
A2DP_VENDOR_MEDIA_CODEC_INFORMATION_CLASSES, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Just a comment)
From a spec perspective, importing A2DP from AVDTP seems like a reversed dependencies, but top-down parsing is important for usage. I am thinking whether it's possible to hook parsing table from A2DP, but that will require callers to always import A2DP, or we will need to wrap AVDTP and A2DP stubs inside a public module which is too aggresive for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it would be nice to have some better way to have a sort of dependency injection here. I haven't found a great way to do that that's not intrusive. I'll try to think of some better way.
bumble/a2dp.py
Outdated
# pylint: disable=import-outside-toplevel | ||
from .avdtp import MediaPacket # Import here to avoid a circular reference |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one is left
Simple CLI app to play audio to a speaker or headphones with A2DP.
This includes support for playing SBC, AAC and Ogg/Opus files.