• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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