| # Copyright 2018 The ChromiumOS Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """A factory test to calibrate the smart speaker amplifier. |
| |
| Description |
| ----------- |
| The test calibrates the smart speaker amplifier by following steps:: |
| |
| 1. Play silent music to load DSM module. |
| 2. Enter calibration mode. |
| 3. Wait for 3 seconds to get stable Rdc. |
| 4. Read Rdc values from two speakers. |
| 5. Store result to VPD. |
| 6. Quit calibration mode. |
| |
| After this test, it is recommended to run ``RebootStep`` to load calibration |
| data into cras, and run ``SpeakerDMic`` test to verify the calibration result. |
| |
| This test only works for some smart speaker amplifiers because some vendors |
| don't want to share their software to public. |
| |
| Test Procedure |
| -------------- |
| This is an automated test without user interaction. |
| |
| Dependency |
| ---------- |
| - ``alsactl-tlv`` |
| - Device API ``cros.factory.device.audio``. |
| - Device API ``cros.factory.device.vpd``. |
| |
| Examples |
| -------- |
| To calibrate the speaker device hw:1,0, add this into test list: |
| |
| .. test_list:: |
| |
| generic_audio_examples:AudioTests.DSMCalibration |
| |
| See `audio_loop.py <./audio_loop.html>`_ for more details about how to set |
| ``output_dev``. |
| """ |
| |
| import logging |
| import re |
| import time |
| from typing import List |
| import unittest |
| |
| from cros.factory.device import device_utils |
| from cros.factory.test import test_tags |
| from cros.factory.test.utils import audio_utils |
| from cros.factory.utils.arg_utils import Arg |
| from cros.factory.utils import process_utils |
| |
| |
| _VPD_KEY = 'dsm_calib' |
| |
| |
| class DSMCalibrationArgs: |
| output_dev: List[str] |
| num_output_channels: int |
| |
| |
| class DSMCalibrationTest(unittest.TestCase): |
| related_components = (test_tags.TestCategory.SMART_SPEAKER_AMPLIFIER, |
| test_tags.TestCategory.VPD) |
| ARGS = [ |
| Arg('output_dev', list, |
| 'Output ALSA device. [card_name, sub_device].' |
| 'For example: ["audio_card", "0"].', ['0', '0']), |
| Arg('num_output_channels', int, |
| 'Number of output channels.', default=2), |
| ] |
| args: DSMCalibrationArgs |
| |
| def setUp(self): |
| self._dut = device_utils.CreateDUTInterface() |
| self._out_card = self.args.output_dev[0] |
| self._out_device = self.args.output_dev[1] |
| self._sox_process = None |
| |
| def runTest(self): |
| device = f'hw:{self._out_card},{self._out_device}' |
| sox_args = audio_utils.GetPlaySineArgs(2, 48000, device, freq=100, |
| duration_secs=1000) |
| self._sox_process = process_utils.Spawn(sox_args) |
| time.sleep(0.5) |
| |
| # Enter calibration mode and wait 3 secs. |
| self._dut.CheckCall([ |
| 'alsactl-tlv', '-D', self._out_card, '-C', 'numid=28', '-V', |
| '0x0000000,0x00000010,0x03000001,0x00000004']) |
| time.sleep(3) |
| |
| calib_data = self._dut.CheckOutput([ |
| 'alsactl-tlv', '-D', self._out_card, '-C', |
| "name='spk_pb_in dsm 0 lp18 params'"]) |
| logging.info(calib_data) |
| |
| # Output should be something like: |
| # |
| # numid=0,iface=MIXER,name='spk_pb_in dsm 0 lp18 params' |
| # TLV READ - 24 bytes |
| # 0000: 03000012 00000008 08fb41b2 08b6a74b 00000000 00000000 |
| # |
| # calibration offset is the 3rd and 4th hex number. |
| match = re.search('[a-f0-9]+:' + ' ([a-f0-9]+)' * 6, calib_data) |
| if not match: |
| raise RuntimeError( |
| f'Calibration offset not found in output: {calib_data}') |
| |
| vpd_value = ( |
| f'0000000 00000010 01000006 {match.group(3)} 02000006 {match.group(4)}') |
| self._dut.vpd.ro.Update({_VPD_KEY: vpd_value}) |
| |
| # Quit calibration mode. |
| self._dut.CheckCall([ |
| 'alsactl-tlv', '-D', self._out_card, '-C', 'numid=28', '-V', |
| '0x0000000,0x00000010,0x03000001,0x00000001']) |
| |
| def tearDown(self): |
| if self._sox_process: |
| self._sox_process.terminate() |