xref: /aosp_15_r20/external/autotest/server/site_tests/audio_AudioVolume/audio_AudioVolume.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1# Lint as: python2, python3
2# Copyright 2016 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5"""This is a server side audio volume test using the Chameleon board."""
6
7import logging
8import os
9import time
10
11from autotest_lib.client.common_lib import error
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_ids
15from autotest_lib.client.cros.chameleon import chameleon_audio_helper
16from autotest_lib.server.cros.audio import audio_test
17
18
19class audio_AudioVolume(audio_test.AudioTest):
20    """Server side audio volume test.
21
22    This test talks to a Chameleon board and a Cros device to verify
23    audio volume function of the Cros device.
24
25    """
26    version = 1
27    RECORD_SECONDS = 8
28    DELAY_AFTER_BINDING = 0.5
29    DELAY_BEFORE_PLAYBACK = 0.5
30
31    def run_once(self, source_id):
32        """Running audio volume test.
33
34        @param source_id: An ID defined in chameleon_audio_ids for source.
35        """
36
37        def get_recorder_id(source_id):
38            """ Get corresponding recorder_id for the source_id."""
39            if source_id == chameleon_audio_ids.CrosIds.SPEAKER:
40                return chameleon_audio_ids.ChameleonIds.MIC
41            elif source_id == chameleon_audio_ids.CrosIds.HEADPHONE:
42                return chameleon_audio_ids.ChameleonIds.LINEIN
43            elif source_id == chameleon_audio_ids.CrosIds.HDMI:
44                return chameleon_audio_ids.ChameleonIds.HDMI
45            elif source_id == chameleon_audio_ids.CrosIds.USBOUT:
46                return chameleon_audio_ids.ChameleonIds.USBIN
47            return None
48
49        def get_volume_spec(source_id):
50            """ Get corresponding volume spec for the source_id.
51
52            @return volume_spec: A tuple (low_volume, high_volume, ratio).
53                            Low volume and high volume specifies the two volumes
54                            used in the test, and ratio specifies the
55                            highest acceptable value for
56                            recorded_volume_low / recorded_volume_high.
57                            For example, (50, 100, 0.2) asserts that
58                            (recorded magnitude at volume 50) should be lower
59                            than (recorded magnitude at volume 100) * 0.2.
60            """
61            if source_id == chameleon_audio_ids.CrosIds.SPEAKER:
62                return (50, 100, 0.85)
63            elif source_id == chameleon_audio_ids.CrosIds.HEADPHONE:
64                return (40, 80, 0.5)
65            elif source_id == chameleon_audio_ids.CrosIds.HDMI:
66                return (40, 80, 0.2)
67            elif source_id == chameleon_audio_ids.CrosIds.USBOUT:
68                return (40, 80, 0.2)
69            return None
70
71        def get_golden_file(source_id):
72            """ Create the golden file for the source_id. """
73            if source_id == chameleon_audio_ids.CrosIds.SPEAKER:
74                return audio_test_data.GenerateAudioTestData(
75                        path=os.path.join(self.bindir, 'fix_440_16.raw'),
76                        duration_secs=10,
77                        frequencies=[440, 440])
78            return audio_test_data.GenerateAudioTestData(
79                    path=os.path.join(self.bindir, 'fix_2k_1k_16.raw'),
80                    duration_secs=10,
81                    frequencies=[2000, 1000])
82
83        if (source_id == chameleon_audio_ids.CrosIds.SPEAKER
84                    and not audio_test_utils.has_internal_speaker(self.host)):
85            return
86
87        golden_file = get_golden_file(source_id)
88
89        source = self.widget_factory.create_widget(source_id)
90
91        recorder_id = get_recorder_id(source_id)
92        recorder = self.widget_factory.create_widget(recorder_id)
93
94        binder = None
95        # Chameleon Mic does not need binding.
96        if recorder_id != chameleon_audio_ids.ChameleonIds.MIC:
97            binder = self.widget_factory.create_binder(source, recorder)
98
99        low_volume, high_volume, highest_ratio = get_volume_spec(source_id)
100        ignore_frequencies = [50, 60]
101
102        second_peak_ratio = audio_test_utils.get_second_peak_ratio(
103                source_id=source_id, recorder_id=recorder_id, is_hsp=False)
104
105        with chameleon_audio_helper.bind_widgets(binder):
106            # Checks the node selected by cras is correct.
107            time.sleep(self.DELAY_AFTER_BINDING)
108
109            audio_test_utils.dump_cros_audio_logs(
110                    self.host, self.facade, self.resultsdir, 'after_binding')
111
112            node_type = audio_test_utils.cros_port_id_to_cras_node_type(
113                    source.port_id)
114            if node_type == 'HEADPHONE':
115                node_type = audio_test_utils.get_headphone_node(self.host)
116            audio_test_utils.check_and_set_chrome_active_node_types(
117                    self.facade, node_type, None)
118            audio_test_utils.dump_cros_audio_logs(
119                    self.host, self.facade, self.resultsdir, 'after_select')
120            audio_test_utils.check_output_port(self.facade, source.port_id)
121
122            logging.info('Setting playback data on Cros device')
123            source.set_playback_data(golden_file)
124
125            def playback_record(tag):
126                """Playback and record.
127
128                @param tag: file name tag.
129
130                """
131                # Starts recording, waits for some time, and starts playing.
132                # This is to avoid artifact caused by Chameleon codec
133                # initialization. The gap should be removed later.
134                logging.info('Start recording from Chameleon.')
135                recorder.start_recording()
136                time.sleep(self.DELAY_BEFORE_PLAYBACK)
137
138                logging.info('Start playing %s on Cros device',
139                             golden_file.path)
140                source.start_playback()
141
142                time.sleep(self.RECORD_SECONDS)
143                self.facade.check_audio_stream_at_selected_device()
144                recorder.stop_recording()
145                logging.info('Stopped recording from Chameleon.')
146
147                audio_test_utils.dump_cros_audio_logs(
148                        self.host, self.facade, self.resultsdir,
149                        'after_recording_' + 'tag')
150
151                source.stop_playback()
152
153                recorder.read_recorded_binary()
154                logging.info('Read recorded binary from Chameleon.')
155
156                recorder.remove_head(self.DELAY_BEFORE_PLAYBACK)
157                if node_type == "INTERNAL_SPEAKER":
158                    recorder.lowpass_filter(1000)
159                recorded_file = os.path.join(self.resultsdir,
160                                             "recorded_%s.raw" % tag)
161                logging.info('Saving recorded data to %s', recorded_file)
162                recorder.save_file(recorded_file)
163
164            self.facade.set_chrome_active_volume(low_volume)
165            playback_record('low')
166            low_dominant_spectrals = audio_test_utils.check_recorded_frequency(
167                    golden_file,
168                    recorder,
169                    second_peak_ratio=second_peak_ratio,
170                    ignore_frequencies=ignore_frequencies)
171
172            self.facade.set_chrome_active_volume(high_volume)
173            playback_record('high')
174            high_dominant_spectrals = audio_test_utils.check_recorded_frequency(
175                    golden_file,
176                    recorder,
177                    second_peak_ratio=second_peak_ratio,
178                    ignore_frequencies=ignore_frequencies)
179
180            error_messages = []
181            logging.info('low_dominant_spectrals: %s', low_dominant_spectrals)
182            logging.info('high_dominant_spectrals: %s',
183                         high_dominant_spectrals)
184
185            for channel in range(len(low_dominant_spectrals)):
186                _, low_coeff = low_dominant_spectrals[channel]
187                _, high_coeff = high_dominant_spectrals[channel]
188                ratio = low_coeff / high_coeff
189                logging.info('Channel %d volume(at %f) / volume(at %f) = %f',
190                             channel, low_volume, high_volume, ratio)
191                if ratio > highest_ratio:
192                    error_messages.append(
193                            'Channel %d volume ratio: %f is incorrect.' %
194                            (channel, ratio))
195            if error_messages:
196                raise error.TestFail(
197                        'Failed volume check: %s' % ' '.join(error_messages))
198