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