• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2018 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 hotwording test using the Chameleon board."""
6
7import logging
8import os
9import time
10import threading
11
12from autotest_lib.client.cros.audio import audio_test_data
13from autotest_lib.client.cros.chameleon import audio_test_utils
14from autotest_lib.client.cros.chameleon import chameleon_audio_helper
15from autotest_lib.client.cros.chameleon import chameleon_audio_ids
16from autotest_lib.server.cros.audio import audio_test
17from autotest_lib.server.cros.multimedia import remote_facade_factory
18
19
20class audio_AudioBasicHotwording(audio_test.AudioTest):
21    """Server side hotwording test.
22
23    This test talks to a Chameleon board and a Cros device to verify
24    hotwording function of the Cros device.
25
26    """
27    version = 1
28    DELAY_AFTER_BINDING_SECS = 0.5
29    DELAY_AFTER_START_LISTENING_SECS = 1
30    DELAY_AFTER_SUSPEND_SECS = 5
31
32    RECORD_SECONDS = 5
33    SUSPEND_SECONDS = 20
34    RESUME_TIMEOUT_SECS = 60
35
36    def run_once(self, host, suspend=False):
37        """Runs Basic Audio Hotwording test.
38
39        @param host: device under test CrosHost
40
41        @param suspend: True for suspend the device before playing hotword.
42                        False for hotwording test suspend.
43
44        """
45        if (not audio_test_utils.has_hotwording(host)):
46            return
47
48        hotword_file = audio_test_data.HOTWORD_TEST_FILE
49        golden_file = audio_test_data.SIMPLE_FREQUENCY_TEST_1330_FILE
50
51        chameleon_board = host.chameleon
52        factory = remote_facade_factory.RemoteFacadeFactory(
53                host, results_dir=self.resultsdir)
54
55        chameleon_board.setup_and_reset(self.outputdir)
56
57        widget_factory = chameleon_audio_helper.AudioWidgetFactory(
58                factory, host)
59
60        source = widget_factory.create_widget(
61            chameleon_audio_ids.ChameleonIds.LINEOUT)
62        sink = widget_factory.create_widget(
63            chameleon_audio_ids.PeripheralIds.SPEAKER)
64        binder = widget_factory.create_binder(source, sink)
65
66        listener = widget_factory.create_widget(
67            chameleon_audio_ids.CrosIds.HOTWORDING)
68
69        with chameleon_audio_helper.bind_widgets(binder):
70            time.sleep(self.DELAY_AFTER_BINDING_SECS)
71            audio_facade = factory.create_audio_facade()
72
73            audio_test_utils.dump_cros_audio_logs(
74                    host, audio_facade, self.resultsdir, 'after_binding')
75
76            logging.info('Start listening from Cros device.')
77            listener.start_listening()
78            time.sleep(self.DELAY_AFTER_START_LISTENING_SECS)
79
80            audio_test_utils.dump_cros_audio_logs(
81                    host, audio_facade, self.resultsdir,
82                    'after_start_listening')
83
84            if suspend:
85                def suspend_host():
86                    logging.info('Suspend the DUT for %d secs',
87                                 self.SUSPEND_SECONDS)
88                    host.suspend(suspend_time=self.SUSPEND_SECONDS,
89                                 allow_early_resume=True)
90
91                # Folk a thread to suspend the host
92                boot_id = host.get_boot_id()
93                thread = threading.Thread(target=suspend_host)
94                thread.start()
95                suspend_start_time = time.time()
96                time.sleep(self.DELAY_AFTER_SUSPEND_SECS)
97
98            # Starts playing hotword and golden file.
99            # Sleep for 5s for DUT to detect and record the sounds
100            logging.info('Setting hotword playback data on Chameleon')
101            remote_hotword_file_path = source.set_playback_data(hotword_file)
102
103            logging.info('Setting golden playback data on Chameleon')
104            remote_golden_file_path = source.set_playback_data(golden_file)
105
106            logging.info('Start playing %s from Chameleon',
107                         hotword_file.path)
108            source.start_playback_with_path(remote_hotword_file_path)
109            time.sleep(hotword_file.duration_secs)
110
111            logging.info('Start playing %s from Chameleon',
112                         golden_file.path)
113            source.start_playback_with_path(remote_golden_file_path)
114
115            time.sleep(self.RECORD_SECONDS)
116
117            # If the DUT suspended, the server will reconnect to DUT
118            if suspend:
119                host.test_wait_for_resume(boot_id, self.RESUME_TIMEOUT_SECS)
120                real_suspend_time = time.time() - suspend_start_time
121                logging.info('Suspend for %f time.', real_suspend_time)
122
123                # Check the if real suspend time is less than SUSPEND_SECOND
124                if real_suspend_time < self.SUSPEND_SECONDS:
125                    logging.info('Real suspend time is less than '
126                                 'SUSPEND_SECONDS. Hotwording succeeded.')
127                else:
128                    logging.error('Real suspend time is larger than or equal to'
129                                  'SUSPEND_SECONDS. Hostwording failed.')
130
131            listener.stop_listening()
132            logging.info('Stopped listening from Cros device.')
133
134            audio_test_utils.dump_cros_audio_logs(
135                    host, audio_facade, self.resultsdir, 'after_listening')
136
137            listener.read_recorded_binary()
138            logging.info('Read recorded binary from Cros device.')
139
140        recorded_file = os.path.join(self.resultsdir, "recorded.raw")
141        logging.info('Saving recorded data to %s', recorded_file)
142        listener.save_file(recorded_file)
143
144        # Removes the first 5s of recorded data, which included hotword and
145        # the data before hotword.
146        listener.remove_head(5.0)
147        short_recorded_file = os.path.join(self.resultsdir,
148                                           "short_recorded.raw")
149        listener.save_file(short_recorded_file)
150
151        # Cros device only records one channel data. Here we set the channel map
152        # of the recorder to compare the recorded data with left channel of the
153        # test data. This is fine as left and right channel of test data are
154        # identical.
155        listener.channel_map = [0]
156
157        # Compares data by frequency. Audio signal from Chameleon Line-Out to
158        # speaker and finally recorded on Cros device using hotwording chip
159        # has gone through analog processing and through the air.
160        # This suffers from codec artifacts and noise on the path.
161        # Comparing data by frequency is more robust than comparing them by
162        # correlation, which is suitable for fully-digital audio path like USB
163        # and HDMI.
164        audio_test_utils.check_recorded_frequency(golden_file, listener,
165                                                  second_peak_ratio=0.2)
166