Home | History | Annotate | Download | only in audio_AudioAfterSuspend
      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 """This is a server side audio test using the Chameleon board."""
      6 
      7 import logging
      8 import os
      9 import time
     10 import threading
     11 
     12 from autotest_lib.client.common_lib import error
     13 from autotest_lib.client.cros.chameleon import audio_test_utils
     14 from autotest_lib.client.cros.chameleon import chameleon_audio_helper
     15 from autotest_lib.client.cros.chameleon import chameleon_audio_ids
     16 from autotest_lib.server.cros.audio import audio_test
     17 from autotest_lib.server.cros.multimedia import remote_facade_factory
     18 
     19 
     20 class audio_AudioAfterSuspend(audio_test.AudioTest):
     21     """Server side audio test.
     22 
     23     This test talks to a Chameleon board and a Cros device to verify
     24     audio function of the Cros device.
     25 
     26     """
     27     version = 1
     28     DELAY_BEFORE_RECORD_SECONDS = 0.5
     29     RECORD_SECONDS = 5
     30     RESUME_TIMEOUT_SECS = 60
     31     SHORT_WAIT = 2
     32     SUSPEND_SECONDS = 30
     33 
     34 
     35     def action_plug_jack(self, plug_state):
     36         """Calls the audio interface API and plugs/unplugs.
     37 
     38         @param plug_state: plug state to switch to
     39 
     40         """
     41         logging.debug('Plugging' if plug_state else 'Unplugging')
     42         jack_plugger = self.audio_board.get_jack_plugger()
     43         if jack_plugger == None:
     44             logging.debug('Jack plugger is NOT present!')
     45             return
     46         if plug_state:
     47             jack_plugger.plug()
     48         else:
     49             jack_plugger.unplug()
     50         time.sleep(self.SHORT_WAIT)
     51 
     52 
     53     def action_suspend(self, suspend_time=SUSPEND_SECONDS):
     54         """Calls the host method suspend.
     55 
     56         @param suspend_time: time to suspend the device for.
     57 
     58         """
     59         self.host.suspend(suspend_time=suspend_time)
     60 
     61 
     62     def suspend_resume(self, plugged_before_suspend, plugged_after_suspend,
     63                                 plugged_before_resume, test_case):
     64         """Performs the mix of suspend/resume and plug/unplug
     65 
     66         @param plugged_before_suspend: plug state before suspend
     67         @param plugged_after_suspend: plug state after suspend
     68         @param plugged_before_resume: plug state before resume
     69         @param test_case: string identifying test case sequence
     70 
     71         """
     72 
     73         # Suspend
     74         boot_id = self.host.get_boot_id()
     75         thread = threading.Thread(target=self.action_suspend)
     76         thread.start()
     77         try:
     78             self.host.test_wait_for_sleep(self.SUSPEND_SECONDS / 3)
     79         except error.TestFail, ex:
     80             self.errors.append("%s - %s" % (test_case, str(ex)))
     81 
     82         # Plugged after suspended
     83         self.action_plug_jack(plugged_after_suspend)
     84 
     85         # Plugged before resumed
     86         self.action_plug_jack(plugged_before_resume)
     87         try:
     88             self.host.test_wait_for_resume(boot_id, self.RESUME_TIMEOUT_SECS)
     89         except error.TestFail, ex:
     90             self.errors.append("%s - %s" % (test_case, str(ex)))
     91 
     92 
     93     def check_correct_audio_node_selected(self):
     94         """Checks the node selected by Cras is correct."""
     95         audio_test_utils.check_audio_nodes(self.audio_facade, self.audio_nodes)
     96 
     97 
     98     def play_and_record(self, source_widget, recorder_widget):
     99         """Plays and records audio
    100 
    101         @param source_widget: widget to do the playback
    102         @param recorder_widget: widget to do the recording
    103 
    104         """
    105         audio_test_utils.dump_cros_audio_logs(
    106                 self.host, self.audio_facade, self.resultsdir,
    107                 'before_playback')
    108 
    109         self.check_correct_audio_node_selected()
    110 
    111         # Play, wait for some time, and then start recording.
    112         # This is to avoid artifact caused by codec initialization.
    113         source_widget.set_playback_data(self.golden_file)
    114         logging.debug('Start playing %s', self.golden_file.path)
    115         source_widget.start_playback()
    116 
    117         time.sleep(self.DELAY_BEFORE_RECORD_SECONDS)
    118         logging.debug('Start recording.')
    119         recorder_widget.start_recording()
    120 
    121         time.sleep(self.RECORD_SECONDS)
    122 
    123         recorder_widget.stop_recording()
    124         logging.debug('Stopped recording.')
    125 
    126         audio_test_utils.dump_cros_audio_logs(
    127                 self.host, self.audio_facade, self.resultsdir,
    128                 'after_recording')
    129 
    130         recorder_widget.read_recorded_binary()
    131 
    132 
    133     def save_and_check_data(self, recorder_widget):
    134         """Saves and checks the data from the recorder
    135 
    136         @param recorder_widget: recorder widget to save data from
    137 
    138         @returns (success, error_message): success is True if audio comparison
    139                                            is successful, False otherwise.
    140                                            error_message contains the error
    141                                            message.
    142 
    143         """
    144         recorded_file = os.path.join(self.resultsdir, "recorded.raw")
    145         logging.debug('Saving recorded data to %s', recorded_file)
    146         recorder_widget.save_file(recorded_file)
    147 
    148         # Removes the beginning of recorded data. This is to avoid artifact
    149         # caused by codec initialization in the beginning of
    150         # recording.
    151         recorder_widget.remove_head(2.0)
    152 
    153         # Removes noise by a lowpass filter.
    154         recorder_widget.lowpass_filter(self.low_pass_freq)
    155         recorded_file = os.path.join(self.resultsdir,
    156                                      "recorded_filtered.raw")
    157         logging.debug('Saving filtered data to %s', recorded_file)
    158         recorder_widget.save_file(recorded_file)
    159 
    160         # Compares data by frequency and returns the result.
    161         try:
    162             audio_test_utils.check_recorded_frequency(
    163                     self.golden_file, recorder_widget,
    164                     second_peak_ratio=self.second_peak_ratio,
    165                     ignore_frequencies=self.ignore_frequencies)
    166         except error.TestFail, e:
    167             return (False, e)
    168 
    169         return (True, None)
    170 
    171 
    172     def run_once(self, host, audio_nodes, golden_data,
    173                  bind_from=None, bind_to=None,
    174                  source=None, recorder=None, plug_status=None):
    175         """Runs the test main workflow
    176 
    177         @param host: A host object representing the DUT.
    178         @param audio_nodes: audio nodes supposed to be selected.
    179         @param golden_data: audio file and low pass filter frequency
    180            the audio file should be test data defined in audio_test_data
    181         @param bind_from: audio originating entity to be binded
    182             should be defined in chameleon_audio_ids
    183         @param bind_to: audio directed_to entity to be binded
    184             should be defined in chameleon_audio_ids
    185         @param source: source widget entity
    186             should be defined in chameleon_audio_ids
    187         @param recorder: recorder widget entity
    188             should be defined in chameleon_audio_ids
    189         @param plug_status: audio channel plug unplug sequence
    190 
    191         """
    192         if (recorder == chameleon_audio_ids.CrosIds.INTERNAL_MIC and
    193             not audio_test_utils.has_internal_microphone(host)):
    194             return
    195 
    196         if (source == chameleon_audio_ids.CrosIds.SPEAKER and
    197             not audio_test_utils.has_internal_speaker(host)):
    198             return
    199 
    200         self.host = host
    201         self.audio_nodes = audio_nodes
    202 
    203         self.second_peak_ratio = audio_test_utils.get_second_peak_ratio(
    204                 source_id=source,
    205                 recorder_id=recorder)
    206 
    207         self.ignore_frequencies = None
    208         if source == chameleon_audio_ids.CrosIds.SPEAKER:
    209             self.ignore_frequencies = [50, 60]
    210 
    211         self.errors = []
    212         self.golden_file, self.low_pass_freq = golden_data
    213         chameleon_board = self.host.chameleon
    214         self.factory = remote_facade_factory.RemoteFacadeFactory(
    215                 self.host, results_dir=self.resultsdir)
    216         self.audio_facade = self.factory.create_audio_facade()
    217         chameleon_board.setup_and_reset(self.outputdir)
    218         widget_factory = chameleon_audio_helper.AudioWidgetFactory(
    219                 self.factory, host)
    220 
    221         # Two widgets are binded in the factory if necessary
    222         binder_widget = None
    223         bind_from_widget = None
    224         bind_to_widget = None
    225         if bind_from != None and bind_to != None:
    226             bind_from_widget = widget_factory.create_widget(bind_from)
    227             bind_to_widget = widget_factory.create_widget(bind_to)
    228             binder_widget = widget_factory.create_binder(bind_from_widget,
    229                                                          bind_to_widget)
    230 
    231         # Additional widgets that could be part of the factory
    232         if source == None:
    233             source_widget = bind_from_widget
    234         else:
    235             source_widget = widget_factory.create_widget(source)
    236         if recorder == None:
    237             recorder_widget = bind_to_widget
    238         else:
    239             recorder_widget = widget_factory.create_widget(recorder)
    240 
    241         self.audio_board = chameleon_board.get_audio_board()
    242 
    243         test_index = 0
    244         for (plugged_before_suspend, plugged_after_suspend, plugged_before_resume,
    245              plugged_after_resume) in plug_status:
    246             test_index += 1
    247 
    248             test_case = ('TEST CASE %d: %s > suspend > %s > %s > resume > %s' %
    249                 (test_index, 'PLUG' if plugged_before_suspend else 'UNPLUG',
    250                  'PLUG' if plugged_after_suspend else 'UNPLUG',
    251                  'PLUG' if plugged_before_resume else 'UNPLUG',
    252                  'PLUG' if plugged_after_resume else 'UNPLUG'))
    253             logging.info(test_case)
    254 
    255             # Plugged status before suspended
    256             self.action_plug_jack(plugged_before_suspend)
    257 
    258             self.suspend_resume(plugged_before_suspend,
    259                                 plugged_after_suspend,
    260                                 plugged_before_resume,
    261                                 test_case)
    262 
    263             # Active (plugged for external) state after resume
    264             self.action_plug_jack(plugged_after_resume)
    265 
    266             if binder_widget != None:
    267                 with chameleon_audio_helper.bind_widgets(binder_widget):
    268                     self.play_and_record(source_widget, recorder_widget)
    269             else:
    270                 self.play_and_record(source_widget, recorder_widget)
    271 
    272             success, error_message = self.save_and_check_data(recorder_widget)
    273             if not success:
    274                 self.errors.append('%s: Comparison failed: %s' %
    275                                    (test_case, error_message))
    276 
    277         if self.errors:
    278             raise error.TestFail('; '.join(set(self.errors)))
    279