Home | History | Annotate | Download | only in audio_AudioBasicBluetoothPlaybackRecord
      1 # Copyright 2015 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 """
      6 This is a server side bluetooth playback/record test using the Chameleon
      7 board.
      8 """
      9 
     10 import logging
     11 import os
     12 import time, threading
     13 
     14 from autotest_lib.client.bin import utils
     15 from autotest_lib.client.common_lib import error
     16 from autotest_lib.client.cros.audio import audio_test_data
     17 from autotest_lib.client.cros.chameleon import audio_test_utils
     18 from autotest_lib.client.cros.chameleon import chameleon_audio_helper
     19 from autotest_lib.client.cros.chameleon import chameleon_audio_ids
     20 from autotest_lib.server.cros.audio import audio_test
     21 
     22 
     23 class audio_AudioBasicBluetoothPlaybackRecord(audio_test.AudioTest):
     24     """Server side bluetooth playback/record audio test.
     25 
     26     This test talks to a Chameleon board and a Cros device to verify
     27     bluetooth playback/record audio function of the Cros device.
     28 
     29     """
     30     version = 1
     31     DELAY_AFTER_DISABLING_MODULE_SECONDS = 30
     32     DELAY_AFTER_DISCONNECT_SECONDS = 5
     33     DELAY_AFTER_ENABLING_MODULE_SECONDS = 10
     34     DELAY_AFTER_RECONNECT_SECONDS = 5
     35     DELAY_BEFORE_RECORD_SECONDS = 2
     36     RECORD_SECONDS = 5
     37     SLEEP_AFTER_RECORD_SECONDS = 5
     38     SUSPEND_SECONDS = 30
     39     PRC_RECONNECT_TIMEOUT = 60
     40     BLUETOOTH_RECONNECT_TIMEOUT_SECS = 30
     41 
     42     def disconnect_connect_bt(self, link):
     43         """Performs disconnect and connect BT module
     44 
     45         @param link: binder link to control BT adapter
     46 
     47         """
     48 
     49         logging.info("Disconnecting BT module...")
     50         link.adapter_disconnect_module()
     51         time.sleep(self.DELAY_AFTER_DISCONNECT_SECONDS)
     52         audio_test_utils.check_audio_nodes(self.audio_facade,
     53                                            (['INTERNAL_SPEAKER'],
     54                                             ['INTERNAL_MIC']))
     55         logging.info("Connecting BT module...")
     56         link.adapter_connect_module()
     57         time.sleep(self.DELAY_AFTER_RECONNECT_SECONDS)
     58 
     59 
     60     def disable_enable_bt(self, link):
     61         """Performs turn off and then on BT module
     62 
     63         @param link: binder playback link to control BT adapter
     64 
     65         """
     66 
     67         logging.info("Turning off BT module...")
     68         link.disable_bluetooth_module()
     69         time.sleep(self.DELAY_AFTER_DISABLING_MODULE_SECONDS)
     70         audio_test_utils.check_audio_nodes(self.audio_facade,
     71                                            (['INTERNAL_SPEAKER'],
     72                                             ['INTERNAL_MIC']))
     73         logging.info("Turning on BT module...")
     74         link.enable_bluetooth_module()
     75         time.sleep(self.DELAY_AFTER_ENABLING_MODULE_SECONDS)
     76         logging.info("Connecting BT module...")
     77         link.adapter_connect_module()
     78         time.sleep(self.DELAY_AFTER_RECONNECT_SECONDS)
     79 
     80 
     81     def bluetooth_nodes_plugged(self):
     82         """Checks if bluetooth nodes are plugged.
     83 
     84         @returns: True if bluetooth nodes are plugged. False otherwise.
     85 
     86         """
     87         return audio_test_utils.bluetooth_nodes_plugged(self.audio_facade)
     88 
     89 
     90     def dump_logs_after_nodes_changed(self):
     91         """Dumps the log after unexpected NodesChanged signal happens."""
     92         audio_test_utils.dump_cros_audio_logs(
     93                 self.host, self.audio_facade, self.resultsdir,
     94                 'after_nodes_changed')
     95 
     96 
     97     def run_once(self, host, suspend=False,
     98                  disable=False, disconnect=False, check_quality=False):
     99         """Running Bluetooth basic audio tests
    100 
    101         @param host: device under test host
    102         @param suspend: suspend flag to enable suspend before play/record
    103         @param disable: disable flag to disable BT module before play/record
    104         @param disconnect: disconnect flag to disconnect BT module
    105             before play/record
    106         @param check_quality: flag to check audio quality.
    107 
    108         """
    109         self.host = host
    110 
    111         # Bluetooth HSP/HFP profile only supports one channel
    112         # playback/recording. So we should use simple frequency
    113         # test file which contains identical sine waves in two
    114         # channels.
    115         golden_file = audio_test_data.SIMPLE_FREQUENCY_TEST_FILE
    116 
    117         factory = self.create_remote_facade_factory(host)
    118         self.audio_facade = factory.create_audio_facade()
    119 
    120         chameleon_board = host.chameleon
    121         chameleon_board.reset()
    122 
    123         widget_factory = chameleon_audio_helper.AudioWidgetFactory(
    124                 factory, host)
    125 
    126         playback_source = widget_factory.create_widget(
    127             chameleon_audio_ids.CrosIds.BLUETOOTH_HEADPHONE)
    128         playback_bluetooth_widget = widget_factory.create_widget(
    129             chameleon_audio_ids.PeripheralIds.BLUETOOTH_DATA_RX)
    130         playback_recorder = widget_factory.create_widget(
    131             chameleon_audio_ids.ChameleonIds.LINEIN)
    132         playback_binder = widget_factory.create_binder(
    133                 playback_source, playback_bluetooth_widget, playback_recorder)
    134 
    135         record_source = widget_factory.create_widget(
    136             chameleon_audio_ids.ChameleonIds.LINEOUT)
    137         record_bluetooth_widget = widget_factory.create_widget(
    138             chameleon_audio_ids.PeripheralIds.BLUETOOTH_DATA_TX)
    139         record_recorder = widget_factory.create_widget(
    140             chameleon_audio_ids.CrosIds.BLUETOOTH_MIC)
    141         record_binder = widget_factory.create_binder(
    142                 record_source, record_bluetooth_widget, record_recorder)
    143 
    144         with chameleon_audio_helper.bind_widgets(playback_binder):
    145             with chameleon_audio_helper.bind_widgets(record_binder):
    146 
    147                 audio_test_utils.dump_cros_audio_logs(
    148                         host, self.audio_facade, self.resultsdir,
    149                         'after_binding')
    150 
    151                 # Checks the input node selected by Cras is internal microphone.
    152                 # Checks crbug.com/495537 for the reason to lower bluetooth
    153                 # microphone priority.
    154                 if audio_test_utils.has_internal_microphone(host):
    155                     audio_test_utils.check_audio_nodes(self.audio_facade,
    156                                                        (None, ['INTERNAL_MIC']))
    157 
    158                 self.audio_facade.set_selected_output_volume(80)
    159 
    160                 # Selecting input nodes needs to be after setting volume because
    161                 # after setting volume, Cras notifies Chrome there is changes
    162                 # in nodes, and Chrome selects the output/input nodes based
    163                 # on its preference again. See crbug.com/535643.
    164 
    165                 # Selects bluetooth mic to be the active input node.
    166                 if audio_test_utils.has_internal_microphone(host):
    167                     self.audio_facade.set_chrome_active_node_type(
    168                             None, 'BLUETOOTH')
    169 
    170                 # Checks the node selected by Cras is correct.
    171                 audio_test_utils.check_audio_nodes(self.audio_facade,
    172                                                    (['BLUETOOTH'],
    173                                                     ['BLUETOOTH']))
    174 
    175                 # Setup the playback data. This step is time consuming.
    176                 playback_source.set_playback_data(golden_file)
    177                 record_source.set_playback_data(golden_file)
    178 
    179                 # Create links to control disconnect and off/on BT adapter.
    180                 link = playback_binder.get_binders()[0].get_link()
    181 
    182                 if disable:
    183                     self.disable_enable_bt(link)
    184                 if disconnect:
    185                     self.disconnect_connect_bt(link)
    186                 if suspend:
    187                     audio_test_utils.suspend_resume(host, self.SUSPEND_SECONDS)
    188 
    189                 if disable or disconnect or suspend:
    190                     audio_test_utils.dump_cros_audio_logs(
    191                             host, self.audio_facade, self.resultsdir,
    192                             'after_action')
    193 
    194                 utils.poll_for_condition(condition=factory.ready,
    195                                          timeout=self.PRC_RECONNECT_TIMEOUT,
    196                                          desc='multimedia server reconnect')
    197 
    198                 # Gives DUT some time to auto-reconnect bluetooth after resume.
    199                 if suspend:
    200                     utils.poll_for_condition(
    201                             condition=self.bluetooth_nodes_plugged,
    202                             timeout=self.BLUETOOTH_RECONNECT_TIMEOUT_SECS,
    203                             desc='bluetooth node auto-reconnect after suspend')
    204 
    205                 if audio_test_utils.has_internal_microphone(host):
    206                     # Select again BT input, as default input node is
    207                     # INTERNAL_MIC.
    208                     self.audio_facade.set_chrome_active_node_type(
    209                             None, 'BLUETOOTH')
    210 
    211                 with audio_test_utils.monitor_no_nodes_changed(
    212                         self.audio_facade, self.dump_logs_after_nodes_changed):
    213                     # Checks the node selected by Cras is correct again.
    214                     audio_test_utils.check_audio_nodes(self.audio_facade,
    215                                                        (['BLUETOOTH'],
    216                                                         ['BLUETOOTH']))
    217 
    218                     # Starts playing, waits for some time, and then starts
    219                     # recording. This is to avoid artifact caused by codec
    220                     # initialization.
    221                     logging.info('Start playing %s on Cros device',
    222                                  golden_file.path)
    223                     playback_source.start_playback()
    224                     logging.info('Start playing %s on Chameleon device',
    225                                  golden_file.path)
    226                     record_source.start_playback()
    227 
    228                     time.sleep(self.DELAY_BEFORE_RECORD_SECONDS)
    229                     logging.info('Start recording from Chameleon.')
    230                     playback_recorder.start_recording()
    231                     logging.info('Start recording from Cros device.')
    232                     record_recorder.start_recording()
    233 
    234                     time.sleep(self.RECORD_SECONDS)
    235 
    236                     playback_recorder.stop_recording()
    237                     logging.info('Stopped recording from Chameleon.')
    238                     record_recorder.stop_recording()
    239                     logging.info('Stopped recording from Cros device.')
    240 
    241                     audio_test_utils.dump_cros_audio_logs(
    242                             host, self.audio_facade, self.resultsdir,
    243                             'after_recording')
    244 
    245                     # Sleeps until playback data ends to prevent audio from
    246                     # going to internal speaker.
    247                     time.sleep(self.SLEEP_AFTER_RECORD_SECONDS)
    248 
    249                     # Gets the recorded data. This step is time consuming.
    250                     playback_recorder.read_recorded_binary()
    251                     logging.info('Read recorded binary from Chameleon.')
    252                     record_recorder.read_recorded_binary()
    253                     logging.info('Read recorded binary from Chameleon.')
    254 
    255                     recorded_file = os.path.join(
    256                             self.resultsdir, "playback_recorded.raw")
    257                     logging.info('Playback: Saving recorded data to %s',
    258                                  recorded_file)
    259                     playback_recorder.save_file(recorded_file)
    260                     recorded_file = os.path.join(
    261                             self.resultsdir, "record_recorded.raw")
    262                     logging.info('Record: Saving recorded data to %s',
    263                                   recorded_file)
    264                     record_recorder.save_file(recorded_file)
    265 
    266         # Removes the beginning of recorded data. This is to avoid artifact
    267         # caused by Chameleon codec initialization in the beginning of
    268         # recording.
    269         playback_recorder.remove_head(0.5)
    270 
    271         # Removes the beginning of recorded data. This is to avoid artifact
    272         # caused by bluetooth module initialization in the beginning of
    273         # its playback.
    274         record_recorder.remove_head(0.5)
    275 
    276         recorded_file = os.path.join(self.resultsdir, "playback_clipped.raw")
    277         logging.info('Saving clipped data to %s', recorded_file)
    278         playback_recorder.save_file(recorded_file)
    279 
    280         recorded_file = os.path.join(self.resultsdir, "record_clipped.raw")
    281         logging.info('Saving clipped data to %s', recorded_file)
    282         record_recorder.save_file(recorded_file)
    283 
    284         # Compares data by frequency. Audio signal recorded by microphone has
    285         # gone through analog processing and through the air.
    286         # This suffers from codec artifacts and noise on the path.
    287         # Comparing data by frequency is more robust than comparing by
    288         # correlation, which is suitable for fully-digital audio path like USB
    289         # and HDMI.
    290         # Use a second peak ratio that can tolerate more noise because HSP
    291         # is low-quality.
    292         second_peak_ratio = audio_test_utils.HSP_SECOND_PEAK_RATIO
    293 
    294         error_messages = ''
    295         try:
    296             audio_test_utils.check_recorded_frequency(
    297                     golden_file, playback_recorder, check_anomaly=check_quality,
    298                     second_peak_ratio=second_peak_ratio)
    299         except error.TestFail, e:
    300             error_messages += str(e)
    301 
    302         try:
    303             audio_test_utils.check_recorded_frequency(
    304                     golden_file, record_recorder, check_anomaly=check_quality,
    305                     second_peak_ratio=second_peak_ratio)
    306         except error.TestFail, e:
    307             error_messages += str(e)
    308 
    309         if error_messages:
    310             raise error.TestFail(error_messages)
    311