blob: e20b864cc85339ffdff6ecd831ed689cf45fbe85 [file] [log] [blame]
#!/usr/bin/env python3
#
# Copyright (C) 2019 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
"""Stream music through connected device from phone across different
attenuations."""
import time
from acts import asserts
from acts.signals import TestPass
from acts.test_utils.bt.A2dpCodecBaseTest import A2dpCodecBaseTest
from acts.test_utils.bt.A2dpCodecBaseTest import HEADSET_CONTROL_SLEEP_TIME
from acts.test_utils.bt import bt_constants
from acts.test_utils.bt.bt_test_utils import set_bluetooth_codec
from acts.test_utils.bt.loggers import bluetooth_metric_logger as log
DEFAULT_THDN_THRESHOLD = 0.9
PHONE_BT_ENABLE_WAITING_TIME = 10
class BtRangeCodecTest(A2dpCodecBaseTest):
def setup_class(self):
super().setup_class()
self.bt_logger = log.BluetoothMetricLogger.for_test_case()
self.start_time = time.time()
self.attenuator = self.attenuators[0]
req_params = [
'bt_atten_start', 'bt_atten_stop',
'bt_atten_step', 'codecs',
]
opt_params = ['RelayDevice', 'required_devices', 'audio_params']
self.unpack_userparams(req_params, opt_params)
for codec_config in self.codecs:
self.generate_test_case(codec_config)
def generate_test_case(self, codec_config):
def test_case_fn():
self.stream_music_on_codec_vs_atten(codec_config)
test_case_name = 'test_streaming_{}'.format('_'.join(
str(codec_config[key])
for key in sorted(codec_config.keys(), reverse=True)
))
setattr(self, test_case_name, test_case_fn)
def setup_test(self):
self.attenuator.set_atten(0)
# let phone undiscoverable before headset power cycle
self.android.droid.bluetoothMakeUndiscoverable()
# power cycle headset
self.log.info('power down headset')
self.bt_device.power_off()
time.sleep(HEADSET_CONTROL_SLEEP_TIME)
self.bt_device.power_on()
self.log.info('headset is powered on')
# enable phone BT discoverability after headset paging sequence is done
# to keep phone at master role
time.sleep(PHONE_BT_ENABLE_WAITING_TIME)
self.log.info('Make phone BT in connectable mode')
self.android.droid.bluetoothMakeConnectable()
super().setup_test()
def teardown_test(self):
super().teardown_test()
self.bt_device.power_off()
# after the test, reset the attenuation
self.attenuator.set_atten(0)
def generate_proto(self, data_points, codec_type, sample_rate,
bits_per_sample, channel_mode):
"""Generate a results protobuf.
Args:
data_points: list of dicts representing info to go into
AudioTestDataPoint protobuffer message.
codec_type: The codec type config to store in the proto.
sample_rate: The sample rate config to store in the proto.
bits_per_sample: The bits per sample config to store in the proto.
channel_mode: The channel mode config to store in the proto.
Returns:
dict: Dictionary with key 'proto' mapping to serialized protobuf,
'proto_ascii' mapping to human readable protobuf info, and 'test'
mapping to the test class name that generated the results.
"""
# Populate protobuf
test_case_proto = self.bt_logger.proto_module.BluetoothAudioTestResult()
for data_point in data_points:
audio_data_proto = test_case_proto.data_points.add()
log.recursive_assign(audio_data_proto, data_point)
codec_proto = test_case_proto.a2dp_codec_config
codec_proto.codec_type = bt_constants.codec_types[codec_type]
codec_proto.sample_rate = int(sample_rate)
codec_proto.bits_per_sample = int(bits_per_sample)
codec_proto.channel_mode = bt_constants.channel_modes[channel_mode]
self.bt_logger.add_config_data_to_proto(test_case_proto,
self.android,
self.bt_device)
self.bt_logger.add_proto_to_results(test_case_proto,
self.__class__.__name__)
proto_dict = self.bt_logger.get_proto_dict(self.__class__.__name__,
test_case_proto)
del proto_dict["proto_ascii"]
return proto_dict
def stream_music_on_codec_vs_atten(self, codec_config):
attenuation_range = range(self.bt_atten_start,
self.bt_atten_stop + 1,
self.bt_atten_step)
data_points = []
codec_set = set_bluetooth_codec(self.android, **codec_config)
asserts.assert_true(codec_set, 'Codec configuration failed.')
#loop RSSI with the same codec setting
for atten in attenuation_range:
self.attenuator.set_atten(atten)
self.log.info('atten %d', atten)
self.play_and_record_audio()
time_from_start = int((time.time() - self.start_time) * 1000)
thdns = self.run_thdn_analysis()
stream_duration = int(self.mic.get_last_record_duration_millis())
data_point = {
'timestamp_since_beginning_of_test_millis': time_from_start,
'audio_streaming_duration_millis': stream_duration,
'attenuation_db': atten,
'total_harmonic_distortion_plus_noise_percent': thdns[0] * 100
}
data_points.append(data_point)
self.log.info('attenuation is %d', atten)
self.log.info('THD+N result is %s', thdns)
for thdn in thdns:
if thdn >= self.user_params.get('thdn_threshold',
DEFAULT_THDN_THRESHOLD):
self.log.info(
'stop increasing attenuation and '
'get into next codec test. THD+N=, %s', str(thdn)
)
proto_dict = self.generate_proto(data_points, **codec_config)
raise TestPass(
'test run through attenuations before audio is broken.'
'Successfully recorded and analyzed audio.',
extras=proto_dict)
proto_dict = self.generate_proto(data_points, **codec_config)
raise TestPass(
'test run through all attenuations.'
'Successfully recorded and analyzed audio.',
extras=proto_dict)