blob: 2d4007e97e4c718fabe1b8bc02b004b485099520 [file] [log] [blame]
# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# This is a factory test to test the audio. Operator will test both record and
# playback for headset and built-in audio. Recordings are played back for
# confirmation. An additional pre-recorded sample is played to confirm speakers
# operate independently.
# We will test each channel of input and output.
import logging
import os
import unittest
import factory_common # pylint: disable=unused-import
from cros.factory.device import device_utils
from cros.factory.test.i18n import _
from cros.factory.test.i18n import arg_utils as i18n_arg_utils
from cros.factory.test.i18n import test_ui as i18n_test_ui
from cros.factory.test import test_ui
from cros.factory.test import ui_templates
from cros.factory.utils.arg_utils import Arg
from cros.factory.utils import file_utils
from cros.factory.utils import process_utils
_RECORD_RATE = 48000
_SOUND_DIRECTORY = os.path.join(
os.path.dirname(os.path.realpath(__file__)), '..', '..', 'goofy',
'static', 'sounds')
_MSG_AUDIO_INFO = i18n_test_ui.MakeI18nLabelWithClass(
"Press 'P' to first play a sample for each channel to ensure audio "
'output works.<br>'
"Press 'R' to record {record_sec} seconds, Playback will follow<br>"
'Press space to mark pass',
_MSG_RECORD_INFO = i18n_test_ui.MakeI18nLabelWithClass('Start recording',
<table style="width: 70%%; margin: auto;">
<td align="center"><div id="audio_title"></div></td>
<td><div id="audio_info"></div></td>
_CSS_AUDIO = """
.audio-test-title { font-size: 2em; }
.audio-test-info { font-size: 2em; }
RECORD_VALUE = (1 << 1)
def GetPlaybackRecordLabel(channel):
return i18n_test_ui.MakeI18nLabelWithClass(
'Playback sound (Mic channel {channel})',
def GetPlaybackLabel(channel):
return i18n_test_ui.MakeI18nLabelWithClass(
'Playback sound to channel {channel}', 'audio-test-info', channel=channel)
class AudioBasicTest(unittest.TestCase):
ARGS = [
'audio_title', 'Label Title of audio test',
Arg('audio_conf', str, 'Audio config file path', optional=True),
Arg('initial_actions', list, 'List of tuple (card, actions)', []),
Arg('input_dev', tuple,
'Input ALSA device. (card_name, sub_device).'
'For example: ("audio_card", "0").', ('0', '0')),
Arg('output_dev', tuple,
'Output ALSA device. (card_name, sub_device).'
'For example: ("audio_card", "0").', ('0', '0')),
Arg('output_channels', int, 'number of output channels.', 2),
Arg('input_channels', int, 'number of input channels.', 2),
def setUp(self):
i18n_arg_utils.ParseArg(self, 'audio_title')
self._dut = device_utils.CreateDUTInterface()
if self.args.audio_conf:
# Tansfer input and output device format
self._in_card =[0])
self._in_device = self.args.input_dev[1]
self._out_card =[0])
self._out_device = self.args.output_dev[1]
# Init audio card before show html
for card, action in self.args.initial_actions:
card =, card)
# Initialize frontend presentation
self.ui = test_ui.UI()
self.template = ui_templates.OneSection(self.ui)
self.ui.BindKey('R', self.HandleRecordEvent)
self.ui.BindKey('P', self.HandleSampleEvent)
self.ui.BindKey(test_ui.SPACE_KEY, self.MarkPass)
msg_audio_title = i18n_test_ui.MakeI18nLabelWithClass(
self.args.audio_title, 'audio-test-info')
self.ui.SetHTML(msg_audio_title, id='audio_title')
self.ui.SetHTML(_MSG_AUDIO_INFO, id='audio_info')
self.current_process = None
self.key_press = None
# prevent operator from pressing space directly,
# make sure he presses P and R.
self.event_value = 0
def HandleRecordEvent(self, event):
del event # Unused.
if not self.key_press:
self.key_press = 'R''start record')
self.ui.SetHTML(_MSG_RECORD_INFO, id='audio_info')
dut_record_file_path = self._dut.temp.mktemp(False), self._in_card,
self._in_device, _RECORD_SEC,
self.args.input_channels, _RECORD_RATE)'stop record and start playback')
# playback the record file by each channel.
with file_utils.UnopenedTemporaryFile(suffix='.wav') as full_wav_path:, full_wav_path)
for i in xrange(self.args.input_channels):
with file_utils.UnopenedTemporaryFile(suffix='.wav') as wav_path:
# Get channel i from full_wav_path to a stereo wav_path
# Since most devices support 2 channels.
['sox', full_wav_path, wav_path, 'remix', str(i + 1),
str(i + 1)], log=True, check_call=True)
with self._dut.temp.TempFile() as dut_path:, dut_path)
self.ui.SetHTML(GetPlaybackRecordLabel(i + 1), id='audio_info'), self._out_card,
self._dut.CheckCall(['rm', '-f', dut_record_file_path])
self.ui.SetHTML(_MSG_AUDIO_INFO, id='audio_info')
self.key_press = None
self.event_value |= RECORD_VALUE
def HandleSampleEvent(self, event):
del event # Unused.
if not self.key_press:
self.key_press = 'P''start play sample')
lang = self.ui.GetUILanguage()
for i in xrange(self.args.output_channels):
ogg_path = os.path.join(_SOUND_DIRECTORY, '%d_%s.ogg' % (i + 1, lang))
number_wav_path = '%s.wav' % ogg_path
['sox', ogg_path, '-c1', number_wav_path], check_call=True)
with file_utils.UnopenedTemporaryFile(suffix='.wav') as wav_path:
# we will only keep (i + 1) channel and mute others.
# We use number sound to indicate which channel to be played.
# Create .wav file with n channels but ony has one channel data.
remix_option = ['0'] * self.args.output_channels
remix_option[i] = '1'
['sox', number_wav_path, wav_path, 'remix'] + remix_option,
log=True, check_call=True)
with self._dut.temp.TempFile() as dut_path:, dut_path)
self.ui.SetHTML(GetPlaybackLabel(i + 1), id='audio_info'), self._out_card,
os.unlink(number_wav_path)'stop play sample')
self.ui.SetHTML(_MSG_AUDIO_INFO, id='audio_info')
self.key_press = None
self.event_value |= PLAY_SAMPLE_VALUE
def MarkPass(self, event):
del event # Unused.
if self.event_value == PASS_VALUE:
def runTest(self):