blob: dd6753d925c4be0da4adf08a428a2c8e0a328102 [file] [log] [blame]
# 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()