def encode(*args, to_base64=False): """Encode the given byte args in TLV format. :param args: Even-number, variable length positional arguments repeating a tag followed by a value. :type args: ``bytes`` :param toBase64: Whether to encode the resuting TLV byte sequence to a base64 str. :type toBase64: ``bool`` :return: The args in TLV format :rtype: ``bytes`` if ``toBase64`` is False and ``str`` otherwise. """ if len(args) % 2 != 0: raise ValueError("Even number of args expected (%d given)" % len(args)) pieces = [] for x in range(0, len(args), 2): tag = args[x] data = args[x + 1] total_length = len(data) if len(data) <= 255: encoded = tag + struct.pack("B", total_length) + data else: encoded = b"" for y in range(0, total_length // 255): encoded = encoded + tag + b"\xFF" + data[y * 255 : (y + 1) * 255] remaining = total_length % 255 encoded = encoded + tag + struct.pack("B", remaining) + data[-remaining:] pieces.append(encoded) result = b"".join(pieces) return util.to_base64_str(result) if to_base64 else result
def set_endpoints(self, value): """Configure streaming endpoints. Called when iOS sets the SetupEndpoints ``Characteristic``. The endpoint information for the camera should be set as the current value of SetupEndpoints. :param value: The base64-encoded stream session details in TLV format. :param value: ``str`` """ objs = tlv.decode(value, from_base64=True) session_id = UUID(bytes=objs[SETUP_TYPES['SESSION_ID']]) # Extract address info address_tlv = objs[SETUP_TYPES['ADDRESS']] address_info_objs = tlv.decode(address_tlv) is_ipv6 = struct.unpack( '?', address_info_objs[SETUP_ADDR_INFO['ADDRESS_VER']])[0] address = address_info_objs[SETUP_ADDR_INFO['ADDRESS']].decode('utf8') target_video_port = struct.unpack( '<H', address_info_objs[SETUP_ADDR_INFO['VIDEO_RTP_PORT']])[0] target_audio_port = struct.unpack( '<H', address_info_objs[SETUP_ADDR_INFO['AUDIO_RTP_PORT']])[0] # Video SRTP Params video_srtp_tlv = objs[SETUP_TYPES['VIDEO_SRTP_PARAM']] video_info_objs = tlv.decode(video_srtp_tlv) video_crypto_suite = video_info_objs[SETUP_SRTP_PARAM['CRYPTO']][0] video_master_key = video_info_objs[SETUP_SRTP_PARAM['MASTER_KEY']] video_master_salt = video_info_objs[SETUP_SRTP_PARAM['MASTER_SALT']] # Audio SRTP Params audio_srtp_tlv = objs[SETUP_TYPES['AUDIO_SRTP_PARAM']] audio_info_objs = tlv.decode(audio_srtp_tlv) audio_crypto_suite = audio_info_objs[SETUP_SRTP_PARAM['CRYPTO']][0] audio_master_key = audio_info_objs[SETUP_SRTP_PARAM['MASTER_KEY']] audio_master_salt = audio_info_objs[SETUP_SRTP_PARAM['MASTER_SALT']] logger.debug( 'Received endpoint configuration:' '\nsession_id: %s\naddress: %s\nis_ipv6: %s' '\ntarget_video_port: %s\ntarget_audio_port: %s' '\nvideo_crypto_suite: %s\nvideo_srtp: %s' '\naudio_crypto_suite: %s\naudio_srtp: %s', session_id, address, is_ipv6, target_video_port, target_audio_port, video_crypto_suite, to_base64_str(video_master_key + video_master_salt), audio_crypto_suite, to_base64_str(audio_master_key + audio_master_salt)) # Configure the SetupEndpoints response if self.has_srtp: video_srtp_tlv = tlv.encode( SETUP_SRTP_PARAM['CRYPTO'], SRTP_CRYPTO_SUITES['AES_CM_128_HMAC_SHA1_80'], SETUP_SRTP_PARAM['MASTER_KEY'], video_master_key, SETUP_SRTP_PARAM['MASTER_SALT'], video_master_salt) audio_srtp_tlv = tlv.encode( SETUP_SRTP_PARAM['CRYPTO'], SRTP_CRYPTO_SUITES['AES_CM_128_HMAC_SHA1_80'], SETUP_SRTP_PARAM['MASTER_KEY'], audio_master_key, SETUP_SRTP_PARAM['MASTER_SALT'], audio_master_salt) else: video_srtp_tlv = NO_SRTP audio_srtp_tlv = NO_SRTP video_ssrc = int.from_bytes(os.urandom(3), byteorder="big") audio_ssrc = int.from_bytes(os.urandom(3), byteorder="big") res_address_tlv = tlv.encode(SETUP_ADDR_INFO['ADDRESS_VER'], self.stream_address_isv6, SETUP_ADDR_INFO['ADDRESS'], self.stream_address.encode('utf-8'), SETUP_ADDR_INFO['VIDEO_RTP_PORT'], struct.pack('<H', target_video_port), SETUP_ADDR_INFO['AUDIO_RTP_PORT'], struct.pack('<H', target_audio_port)) response_tlv = tlv.encode(SETUP_TYPES['SESSION_ID'], session_id.bytes, SETUP_TYPES['STATUS'], SETUP_STATUS['SUCCESS'], SETUP_TYPES['ADDRESS'], res_address_tlv, SETUP_TYPES['VIDEO_SRTP_PARAM'], video_srtp_tlv, SETUP_TYPES['AUDIO_SRTP_PARAM'], audio_srtp_tlv, SETUP_TYPES['VIDEO_SSRC'], struct.pack('<I', video_ssrc), SETUP_TYPES['AUDIO_SSRC'], struct.pack('<I', audio_ssrc), to_base64=True) self.sessions[session_id] = { 'id': session_id, 'address': address, 'v_port': target_video_port, 'v_srtp_key': to_base64_str(video_master_key + video_master_salt), 'v_ssrc': video_ssrc, 'a_port': target_audio_port, 'a_srtp_key': to_base64_str(audio_master_key + audio_master_salt), 'a_ssrc': audio_ssrc } self.get_service('CameraRTPStreamManagement')\ .get_characteristic('SetupEndpoints')\ .set_value(response_tlv)
def get_supported_audio_stream_config(audio_params): """Return a tlv representation of the supported audio stream configuration. iOS supports only AACELD and OPUS Expected audio parameters: - codecs - comfort_noise :param audio_params: Supported audio configurations :type audio_params: dict """ has_supported_codec = False configs = b'' for codec_param in audio_params['codecs']: param_type = codec_param['type'] if param_type == 'OPUS': has_supported_codec = True codec = AUDIO_CODEC_TYPES['OPUS'] bitrate = AUDIO_CODEC_PARAM_BIT_RATE_TYPES['VARIABLE'] elif param_type == 'AAC-eld': has_supported_codec = True codec = AUDIO_CODEC_TYPES['AACELD'] bitrate = AUDIO_CODEC_PARAM_BIT_RATE_TYPES['VARIABLE'] else: logger.warning('Unsupported codec %s', param_type) continue param_samplerate = codec_param['samplerate'] if param_samplerate == 8: samplerate = AUDIO_CODEC_PARAM_SAMPLE_RATE_TYPES['KHZ_8'] elif param_samplerate == 16: samplerate = AUDIO_CODEC_PARAM_SAMPLE_RATE_TYPES['KHZ_16'] elif param_samplerate == 24: samplerate = AUDIO_CODEC_PARAM_SAMPLE_RATE_TYPES['KHZ_24'] else: logger.warning('Unsupported sample rate %s', param_samplerate) continue param_tlv = tlv.encode(AUDIO_CODEC_PARAM_TYPES['CHANNEL'], b'\x01', AUDIO_CODEC_PARAM_TYPES['BIT_RATE'], bitrate, AUDIO_CODEC_PARAM_TYPES['SAMPLE_RATE'], samplerate) config_tlv = tlv.encode(AUDIO_TYPES['CODEC'], codec, AUDIO_TYPES['CODEC_PARAM'], param_tlv) configs += tlv.encode(SUPPORTED_AUDIO_CODECS_TAG, config_tlv) if not has_supported_codec: logger.warning( 'Client does not support any audio codec that iOS supports.') codec = AUDIO_CODEC_TYPES['OPUS'] bitrate = AUDIO_CODEC_PARAM_BIT_RATE_TYPES['VARIABLE'] samplerate = AUDIO_CODEC_PARAM_SAMPLE_RATE_TYPES['KHZ_24'] param_tlv = tlv.encode(AUDIO_CODEC_PARAM_TYPES['CHANNEL'], b'\x01', AUDIO_CODEC_PARAM_TYPES['BIT_RATE'], bitrate, AUDIO_CODEC_PARAM_TYPES['SAMPLE_RATE'], samplerate) config_tlv = tlv.encode(AUDIO_TYPES['CODEC'], codec, AUDIO_TYPES['CODEC_PARAM'], param_tlv) configs = tlv.encode(SUPPORTED_AUDIO_CODECS_TAG, config_tlv) comfort_noise = byte_bool(audio_params.get('comfort_noise', False)) audio_config = to_base64_str( configs + tlv.encode(SUPPORTED_COMFORT_NOISE_TAG, comfort_noise)) return audio_config