1# Copyright 2021-2022 Google LLC 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# https://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15# ----------------------------------------------------------------------------- 16# Imports 17# ----------------------------------------------------------------------------- 18from __future__ import annotations 19 20import dataclasses 21import struct 22import logging 23from collections.abc import AsyncGenerator 24from typing import List, Callable, Awaitable 25 26from .company_ids import COMPANY_IDENTIFIERS 27from .sdp import ( 28 DataElement, 29 ServiceAttribute, 30 SDP_PUBLIC_BROWSE_ROOT, 31 SDP_BROWSE_GROUP_LIST_ATTRIBUTE_ID, 32 SDP_SERVICE_RECORD_HANDLE_ATTRIBUTE_ID, 33 SDP_SERVICE_CLASS_ID_LIST_ATTRIBUTE_ID, 34 SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID, 35 SDP_BLUETOOTH_PROFILE_DESCRIPTOR_LIST_ATTRIBUTE_ID, 36) 37from .core import ( 38 BT_L2CAP_PROTOCOL_ID, 39 BT_AUDIO_SOURCE_SERVICE, 40 BT_AUDIO_SINK_SERVICE, 41 BT_AVDTP_PROTOCOL_ID, 42 BT_ADVANCED_AUDIO_DISTRIBUTION_SERVICE, 43 name_or_number, 44) 45 46 47# ----------------------------------------------------------------------------- 48# Logging 49# ----------------------------------------------------------------------------- 50logger = logging.getLogger(__name__) 51 52 53# ----------------------------------------------------------------------------- 54# Constants 55# ----------------------------------------------------------------------------- 56# fmt: off 57 58A2DP_SBC_CODEC_TYPE = 0x00 59A2DP_MPEG_1_2_AUDIO_CODEC_TYPE = 0x01 60A2DP_MPEG_2_4_AAC_CODEC_TYPE = 0x02 61A2DP_ATRAC_FAMILY_CODEC_TYPE = 0x03 62A2DP_NON_A2DP_CODEC_TYPE = 0xFF 63 64A2DP_CODEC_TYPE_NAMES = { 65 A2DP_SBC_CODEC_TYPE: 'A2DP_SBC_CODEC_TYPE', 66 A2DP_MPEG_1_2_AUDIO_CODEC_TYPE: 'A2DP_MPEG_1_2_AUDIO_CODEC_TYPE', 67 A2DP_MPEG_2_4_AAC_CODEC_TYPE: 'A2DP_MPEG_2_4_AAC_CODEC_TYPE', 68 A2DP_ATRAC_FAMILY_CODEC_TYPE: 'A2DP_ATRAC_FAMILY_CODEC_TYPE', 69 A2DP_NON_A2DP_CODEC_TYPE: 'A2DP_NON_A2DP_CODEC_TYPE' 70} 71 72 73SBC_SYNC_WORD = 0x9C 74 75SBC_SAMPLING_FREQUENCIES = [ 76 16000, 77 22050, 78 44100, 79 48000 80] 81 82SBC_MONO_CHANNEL_MODE = 0x00 83SBC_DUAL_CHANNEL_MODE = 0x01 84SBC_STEREO_CHANNEL_MODE = 0x02 85SBC_JOINT_STEREO_CHANNEL_MODE = 0x03 86 87SBC_CHANNEL_MODE_NAMES = { 88 SBC_MONO_CHANNEL_MODE: 'SBC_MONO_CHANNEL_MODE', 89 SBC_DUAL_CHANNEL_MODE: 'SBC_DUAL_CHANNEL_MODE', 90 SBC_STEREO_CHANNEL_MODE: 'SBC_STEREO_CHANNEL_MODE', 91 SBC_JOINT_STEREO_CHANNEL_MODE: 'SBC_JOINT_STEREO_CHANNEL_MODE' 92} 93 94SBC_BLOCK_LENGTHS = [4, 8, 12, 16] 95 96SBC_SUBBANDS = [4, 8] 97 98SBC_SNR_ALLOCATION_METHOD = 0x00 99SBC_LOUDNESS_ALLOCATION_METHOD = 0x01 100 101SBC_ALLOCATION_METHOD_NAMES = { 102 SBC_SNR_ALLOCATION_METHOD: 'SBC_SNR_ALLOCATION_METHOD', 103 SBC_LOUDNESS_ALLOCATION_METHOD: 'SBC_LOUDNESS_ALLOCATION_METHOD' 104} 105 106MPEG_2_4_AAC_SAMPLING_FREQUENCIES = [ 107 8000, 108 11025, 109 12000, 110 16000, 111 22050, 112 24000, 113 32000, 114 44100, 115 48000, 116 64000, 117 88200, 118 96000 119] 120 121MPEG_2_AAC_LC_OBJECT_TYPE = 0x00 122MPEG_4_AAC_LC_OBJECT_TYPE = 0x01 123MPEG_4_AAC_LTP_OBJECT_TYPE = 0x02 124MPEG_4_AAC_SCALABLE_OBJECT_TYPE = 0x03 125 126MPEG_2_4_OBJECT_TYPE_NAMES = { 127 MPEG_2_AAC_LC_OBJECT_TYPE: 'MPEG_2_AAC_LC_OBJECT_TYPE', 128 MPEG_4_AAC_LC_OBJECT_TYPE: 'MPEG_4_AAC_LC_OBJECT_TYPE', 129 MPEG_4_AAC_LTP_OBJECT_TYPE: 'MPEG_4_AAC_LTP_OBJECT_TYPE', 130 MPEG_4_AAC_SCALABLE_OBJECT_TYPE: 'MPEG_4_AAC_SCALABLE_OBJECT_TYPE' 131} 132 133# fmt: on 134 135 136# ----------------------------------------------------------------------------- 137def flags_to_list(flags, values): 138 result = [] 139 for i, value in enumerate(values): 140 if flags & (1 << (len(values) - i - 1)): 141 result.append(value) 142 return result 143 144 145# ----------------------------------------------------------------------------- 146def make_audio_source_service_sdp_records(service_record_handle, version=(1, 3)): 147 # pylint: disable=import-outside-toplevel 148 from .avdtp import AVDTP_PSM 149 150 version_int = version[0] << 8 | version[1] 151 return [ 152 ServiceAttribute( 153 SDP_SERVICE_RECORD_HANDLE_ATTRIBUTE_ID, 154 DataElement.unsigned_integer_32(service_record_handle), 155 ), 156 ServiceAttribute( 157 SDP_BROWSE_GROUP_LIST_ATTRIBUTE_ID, 158 DataElement.sequence([DataElement.uuid(SDP_PUBLIC_BROWSE_ROOT)]), 159 ), 160 ServiceAttribute( 161 SDP_SERVICE_CLASS_ID_LIST_ATTRIBUTE_ID, 162 DataElement.sequence([DataElement.uuid(BT_AUDIO_SOURCE_SERVICE)]), 163 ), 164 ServiceAttribute( 165 SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID, 166 DataElement.sequence( 167 [ 168 DataElement.sequence( 169 [ 170 DataElement.uuid(BT_L2CAP_PROTOCOL_ID), 171 DataElement.unsigned_integer_16(AVDTP_PSM), 172 ] 173 ), 174 DataElement.sequence( 175 [ 176 DataElement.uuid(BT_AVDTP_PROTOCOL_ID), 177 DataElement.unsigned_integer_16(version_int), 178 ] 179 ), 180 ] 181 ), 182 ), 183 ServiceAttribute( 184 SDP_BLUETOOTH_PROFILE_DESCRIPTOR_LIST_ATTRIBUTE_ID, 185 DataElement.sequence( 186 [ 187 DataElement.sequence( 188 [ 189 DataElement.uuid(BT_ADVANCED_AUDIO_DISTRIBUTION_SERVICE), 190 DataElement.unsigned_integer_16(version_int), 191 ] 192 ) 193 ] 194 ), 195 ), 196 ] 197 198 199# ----------------------------------------------------------------------------- 200def make_audio_sink_service_sdp_records(service_record_handle, version=(1, 3)): 201 # pylint: disable=import-outside-toplevel 202 from .avdtp import AVDTP_PSM 203 204 version_int = version[0] << 8 | version[1] 205 return [ 206 ServiceAttribute( 207 SDP_SERVICE_RECORD_HANDLE_ATTRIBUTE_ID, 208 DataElement.unsigned_integer_32(service_record_handle), 209 ), 210 ServiceAttribute( 211 SDP_BROWSE_GROUP_LIST_ATTRIBUTE_ID, 212 DataElement.sequence([DataElement.uuid(SDP_PUBLIC_BROWSE_ROOT)]), 213 ), 214 ServiceAttribute( 215 SDP_SERVICE_CLASS_ID_LIST_ATTRIBUTE_ID, 216 DataElement.sequence([DataElement.uuid(BT_AUDIO_SINK_SERVICE)]), 217 ), 218 ServiceAttribute( 219 SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID, 220 DataElement.sequence( 221 [ 222 DataElement.sequence( 223 [ 224 DataElement.uuid(BT_L2CAP_PROTOCOL_ID), 225 DataElement.unsigned_integer_16(AVDTP_PSM), 226 ] 227 ), 228 DataElement.sequence( 229 [ 230 DataElement.uuid(BT_AVDTP_PROTOCOL_ID), 231 DataElement.unsigned_integer_16(version_int), 232 ] 233 ), 234 ] 235 ), 236 ), 237 ServiceAttribute( 238 SDP_BLUETOOTH_PROFILE_DESCRIPTOR_LIST_ATTRIBUTE_ID, 239 DataElement.sequence( 240 [ 241 DataElement.sequence( 242 [ 243 DataElement.uuid(BT_ADVANCED_AUDIO_DISTRIBUTION_SERVICE), 244 DataElement.unsigned_integer_16(version_int), 245 ] 246 ) 247 ] 248 ), 249 ), 250 ] 251 252 253# ----------------------------------------------------------------------------- 254@dataclasses.dataclass 255class SbcMediaCodecInformation: 256 ''' 257 A2DP spec - 4.3.2 Codec Specific Information Elements 258 ''' 259 260 sampling_frequency: int 261 channel_mode: int 262 block_length: int 263 subbands: int 264 allocation_method: int 265 minimum_bitpool_value: int 266 maximum_bitpool_value: int 267 268 SAMPLING_FREQUENCY_BITS = {16000: 1 << 3, 32000: 1 << 2, 44100: 1 << 1, 48000: 1} 269 CHANNEL_MODE_BITS = { 270 SBC_MONO_CHANNEL_MODE: 1 << 3, 271 SBC_DUAL_CHANNEL_MODE: 1 << 2, 272 SBC_STEREO_CHANNEL_MODE: 1 << 1, 273 SBC_JOINT_STEREO_CHANNEL_MODE: 1, 274 } 275 BLOCK_LENGTH_BITS = {4: 1 << 3, 8: 1 << 2, 12: 1 << 1, 16: 1} 276 SUBBANDS_BITS = {4: 1 << 1, 8: 1} 277 ALLOCATION_METHOD_BITS = { 278 SBC_SNR_ALLOCATION_METHOD: 1 << 1, 279 SBC_LOUDNESS_ALLOCATION_METHOD: 1, 280 } 281 282 @staticmethod 283 def from_bytes(data: bytes) -> SbcMediaCodecInformation: 284 sampling_frequency = (data[0] >> 4) & 0x0F 285 channel_mode = (data[0] >> 0) & 0x0F 286 block_length = (data[1] >> 4) & 0x0F 287 subbands = (data[1] >> 2) & 0x03 288 allocation_method = (data[1] >> 0) & 0x03 289 minimum_bitpool_value = (data[2] >> 0) & 0xFF 290 maximum_bitpool_value = (data[3] >> 0) & 0xFF 291 return SbcMediaCodecInformation( 292 sampling_frequency, 293 channel_mode, 294 block_length, 295 subbands, 296 allocation_method, 297 minimum_bitpool_value, 298 maximum_bitpool_value, 299 ) 300 301 @classmethod 302 def from_discrete_values( 303 cls, 304 sampling_frequency: int, 305 channel_mode: int, 306 block_length: int, 307 subbands: int, 308 allocation_method: int, 309 minimum_bitpool_value: int, 310 maximum_bitpool_value: int, 311 ) -> SbcMediaCodecInformation: 312 return SbcMediaCodecInformation( 313 sampling_frequency=cls.SAMPLING_FREQUENCY_BITS[sampling_frequency], 314 channel_mode=cls.CHANNEL_MODE_BITS[channel_mode], 315 block_length=cls.BLOCK_LENGTH_BITS[block_length], 316 subbands=cls.SUBBANDS_BITS[subbands], 317 allocation_method=cls.ALLOCATION_METHOD_BITS[allocation_method], 318 minimum_bitpool_value=minimum_bitpool_value, 319 maximum_bitpool_value=maximum_bitpool_value, 320 ) 321 322 @classmethod 323 def from_lists( 324 cls, 325 sampling_frequencies: List[int], 326 channel_modes: List[int], 327 block_lengths: List[int], 328 subbands: List[int], 329 allocation_methods: List[int], 330 minimum_bitpool_value: int, 331 maximum_bitpool_value: int, 332 ) -> SbcMediaCodecInformation: 333 return SbcMediaCodecInformation( 334 sampling_frequency=sum( 335 cls.SAMPLING_FREQUENCY_BITS[x] for x in sampling_frequencies 336 ), 337 channel_mode=sum(cls.CHANNEL_MODE_BITS[x] for x in channel_modes), 338 block_length=sum(cls.BLOCK_LENGTH_BITS[x] for x in block_lengths), 339 subbands=sum(cls.SUBBANDS_BITS[x] for x in subbands), 340 allocation_method=sum( 341 cls.ALLOCATION_METHOD_BITS[x] for x in allocation_methods 342 ), 343 minimum_bitpool_value=minimum_bitpool_value, 344 maximum_bitpool_value=maximum_bitpool_value, 345 ) 346 347 def __bytes__(self) -> bytes: 348 return bytes( 349 [ 350 (self.sampling_frequency << 4) | self.channel_mode, 351 (self.block_length << 4) 352 | (self.subbands << 2) 353 | self.allocation_method, 354 self.minimum_bitpool_value, 355 self.maximum_bitpool_value, 356 ] 357 ) 358 359 def __str__(self) -> str: 360 channel_modes = ['MONO', 'DUAL_CHANNEL', 'STEREO', 'JOINT_STEREO'] 361 allocation_methods = ['SNR', 'Loudness'] 362 return '\n'.join( 363 # pylint: disable=line-too-long 364 [ 365 'SbcMediaCodecInformation(', 366 f' sampling_frequency: {",".join([str(x) for x in flags_to_list(self.sampling_frequency, SBC_SAMPLING_FREQUENCIES)])}', 367 f' channel_mode: {",".join([str(x) for x in flags_to_list(self.channel_mode, channel_modes)])}', 368 f' block_length: {",".join([str(x) for x in flags_to_list(self.block_length, SBC_BLOCK_LENGTHS)])}', 369 f' subbands: {",".join([str(x) for x in flags_to_list(self.subbands, SBC_SUBBANDS)])}', 370 f' allocation_method: {",".join([str(x) for x in flags_to_list(self.allocation_method, allocation_methods)])}', 371 f' minimum_bitpool_value: {self.minimum_bitpool_value}', 372 f' maximum_bitpool_value: {self.maximum_bitpool_value}' ')', 373 ] 374 ) 375 376 377# ----------------------------------------------------------------------------- 378@dataclasses.dataclass 379class AacMediaCodecInformation: 380 ''' 381 A2DP spec - 4.5.2 Codec Specific Information Elements 382 ''' 383 384 object_type: int 385 sampling_frequency: int 386 channels: int 387 rfa: int 388 vbr: int 389 bitrate: int 390 391 OBJECT_TYPE_BITS = { 392 MPEG_2_AAC_LC_OBJECT_TYPE: 1 << 7, 393 MPEG_4_AAC_LC_OBJECT_TYPE: 1 << 6, 394 MPEG_4_AAC_LTP_OBJECT_TYPE: 1 << 5, 395 MPEG_4_AAC_SCALABLE_OBJECT_TYPE: 1 << 4, 396 } 397 SAMPLING_FREQUENCY_BITS = { 398 8000: 1 << 11, 399 11025: 1 << 10, 400 12000: 1 << 9, 401 16000: 1 << 8, 402 22050: 1 << 7, 403 24000: 1 << 6, 404 32000: 1 << 5, 405 44100: 1 << 4, 406 48000: 1 << 3, 407 64000: 1 << 2, 408 88200: 1 << 1, 409 96000: 1, 410 } 411 CHANNELS_BITS = {1: 1 << 1, 2: 1} 412 413 @staticmethod 414 def from_bytes(data: bytes) -> AacMediaCodecInformation: 415 object_type = data[0] 416 sampling_frequency = (data[1] << 4) | ((data[2] >> 4) & 0x0F) 417 channels = (data[2] >> 2) & 0x03 418 rfa = 0 419 vbr = (data[3] >> 7) & 0x01 420 bitrate = ((data[3] & 0x7F) << 16) | (data[4] << 8) | data[5] 421 return AacMediaCodecInformation( 422 object_type, sampling_frequency, channels, rfa, vbr, bitrate 423 ) 424 425 @classmethod 426 def from_discrete_values( 427 cls, 428 object_type: int, 429 sampling_frequency: int, 430 channels: int, 431 vbr: int, 432 bitrate: int, 433 ) -> AacMediaCodecInformation: 434 return AacMediaCodecInformation( 435 object_type=cls.OBJECT_TYPE_BITS[object_type], 436 sampling_frequency=cls.SAMPLING_FREQUENCY_BITS[sampling_frequency], 437 channels=cls.CHANNELS_BITS[channels], 438 rfa=0, 439 vbr=vbr, 440 bitrate=bitrate, 441 ) 442 443 @classmethod 444 def from_lists( 445 cls, 446 object_types: List[int], 447 sampling_frequencies: List[int], 448 channels: List[int], 449 vbr: int, 450 bitrate: int, 451 ) -> AacMediaCodecInformation: 452 return AacMediaCodecInformation( 453 object_type=sum(cls.OBJECT_TYPE_BITS[x] for x in object_types), 454 sampling_frequency=sum( 455 cls.SAMPLING_FREQUENCY_BITS[x] for x in sampling_frequencies 456 ), 457 channels=sum(cls.CHANNELS_BITS[x] for x in channels), 458 rfa=0, 459 vbr=vbr, 460 bitrate=bitrate, 461 ) 462 463 def __bytes__(self) -> bytes: 464 return bytes( 465 [ 466 self.object_type & 0xFF, 467 (self.sampling_frequency >> 4) & 0xFF, 468 (((self.sampling_frequency & 0x0F) << 4) | (self.channels << 2)) & 0xFF, 469 ((self.vbr << 7) | ((self.bitrate >> 16) & 0x7F)) & 0xFF, 470 ((self.bitrate >> 8) & 0xFF) & 0xFF, 471 self.bitrate & 0xFF, 472 ] 473 ) 474 475 def __str__(self) -> str: 476 object_types = [ 477 'MPEG_2_AAC_LC', 478 'MPEG_4_AAC_LC', 479 'MPEG_4_AAC_LTP', 480 'MPEG_4_AAC_SCALABLE', 481 '[4]', 482 '[5]', 483 '[6]', 484 '[7]', 485 ] 486 channels = [1, 2] 487 # pylint: disable=line-too-long 488 return '\n'.join( 489 [ 490 'AacMediaCodecInformation(', 491 f' object_type: {",".join([str(x) for x in flags_to_list(self.object_type, object_types)])}', 492 f' sampling_frequency: {",".join([str(x) for x in flags_to_list(self.sampling_frequency, MPEG_2_4_AAC_SAMPLING_FREQUENCIES)])}', 493 f' channels: {",".join([str(x) for x in flags_to_list(self.channels, channels)])}', 494 f' vbr: {self.vbr}', 495 f' bitrate: {self.bitrate}' ')', 496 ] 497 ) 498 499 500@dataclasses.dataclass 501# ----------------------------------------------------------------------------- 502class VendorSpecificMediaCodecInformation: 503 ''' 504 A2DP spec - 4.7.2 Codec Specific Information Elements 505 ''' 506 507 vendor_id: int 508 codec_id: int 509 value: bytes 510 511 @staticmethod 512 def from_bytes(data: bytes) -> VendorSpecificMediaCodecInformation: 513 (vendor_id, codec_id) = struct.unpack_from('<IH', data, 0) 514 return VendorSpecificMediaCodecInformation(vendor_id, codec_id, data[6:]) 515 516 def __bytes__(self) -> bytes: 517 return struct.pack('<IH', self.vendor_id, self.codec_id, self.value) 518 519 def __str__(self) -> str: 520 # pylint: disable=line-too-long 521 return '\n'.join( 522 [ 523 'VendorSpecificMediaCodecInformation(', 524 f' vendor_id: {self.vendor_id:08X} ({name_or_number(COMPANY_IDENTIFIERS, self.vendor_id & 0xFFFF)})', 525 f' codec_id: {self.codec_id:04X}', 526 f' value: {self.value.hex()}' ')', 527 ] 528 ) 529 530 531# ----------------------------------------------------------------------------- 532@dataclasses.dataclass 533class SbcFrame: 534 sampling_frequency: int 535 block_count: int 536 channel_mode: int 537 subband_count: int 538 payload: bytes 539 540 @property 541 def sample_count(self) -> int: 542 return self.subband_count * self.block_count 543 544 @property 545 def bitrate(self) -> int: 546 return 8 * ((len(self.payload) * self.sampling_frequency) // self.sample_count) 547 548 @property 549 def duration(self) -> float: 550 return self.sample_count / self.sampling_frequency 551 552 def __str__(self) -> str: 553 return ( 554 f'SBC(sf={self.sampling_frequency},' 555 f'cm={self.channel_mode},' 556 f'br={self.bitrate},' 557 f'sc={self.sample_count},' 558 f'size={len(self.payload)})' 559 ) 560 561 562# ----------------------------------------------------------------------------- 563class SbcParser: 564 def __init__(self, read: Callable[[int], Awaitable[bytes]]) -> None: 565 self.read = read 566 567 @property 568 def frames(self) -> AsyncGenerator[SbcFrame, None]: 569 async def generate_frames() -> AsyncGenerator[SbcFrame, None]: 570 while True: 571 # Read 4 bytes of header 572 header = await self.read(4) 573 if len(header) != 4: 574 return 575 576 # Check the sync word 577 if header[0] != SBC_SYNC_WORD: 578 logger.debug('invalid sync word') 579 return 580 581 # Extract some of the header fields 582 sampling_frequency = SBC_SAMPLING_FREQUENCIES[(header[1] >> 6) & 3] 583 blocks = 4 * (1 + ((header[1] >> 4) & 3)) 584 channel_mode = (header[1] >> 2) & 3 585 channels = 1 if channel_mode == SBC_MONO_CHANNEL_MODE else 2 586 subbands = 8 if ((header[1]) & 1) else 4 587 bitpool = header[2] 588 589 # Compute the frame length 590 frame_length = 4 + (4 * subbands * channels) // 8 591 if channel_mode in (SBC_MONO_CHANNEL_MODE, SBC_DUAL_CHANNEL_MODE): 592 frame_length += (blocks * channels * bitpool) // 8 593 else: 594 frame_length += ( 595 (1 if channel_mode == SBC_JOINT_STEREO_CHANNEL_MODE else 0) 596 * subbands 597 + blocks * bitpool 598 ) // 8 599 600 # Read the rest of the frame 601 payload = header + await self.read(frame_length - 4) 602 603 # Emit the next frame 604 yield SbcFrame( 605 sampling_frequency, blocks, channel_mode, subbands, payload 606 ) 607 608 return generate_frames() 609 610 611# ----------------------------------------------------------------------------- 612class SbcPacketSource: 613 def __init__( 614 self, read: Callable[[int], Awaitable[bytes]], mtu: int, codec_capabilities 615 ) -> None: 616 self.read = read 617 self.mtu = mtu 618 self.codec_capabilities = codec_capabilities 619 620 @property 621 def packets(self): 622 async def generate_packets(): 623 # pylint: disable=import-outside-toplevel 624 from .avdtp import MediaPacket # Import here to avoid a circular reference 625 626 sequence_number = 0 627 timestamp = 0 628 frames = [] 629 frames_size = 0 630 max_rtp_payload = self.mtu - 12 - 1 631 632 # NOTE: this doesn't support frame fragments 633 sbc_parser = SbcParser(self.read) 634 async for frame in sbc_parser.frames: 635 print(frame) 636 637 if ( 638 frames_size + len(frame.payload) > max_rtp_payload 639 or len(frames) == 16 640 ): 641 # Need to flush what has been accumulated so far 642 643 # Emit a packet 644 sbc_payload = bytes([len(frames)]) + b''.join( 645 [frame.payload for frame in frames] 646 ) 647 packet = MediaPacket( 648 2, 0, 0, 0, sequence_number, timestamp, 0, [], 96, sbc_payload 649 ) 650 packet.timestamp_seconds = timestamp / frame.sampling_frequency 651 yield packet 652 653 # Prepare for next packets 654 sequence_number += 1 655 sequence_number &= 0xFFFF 656 timestamp += sum((frame.sample_count for frame in frames)) 657 timestamp &= 0xFFFFFFFF 658 frames = [frame] 659 frames_size = len(frame.payload) 660 else: 661 # Accumulate 662 frames.append(frame) 663 frames_size += len(frame.payload) 664 665 return generate_packets() 666