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