| # Copyright 2014 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 module provides the framework for audio tests using chameleon.""" |
| |
| import logging |
| from contextlib import contextmanager |
| |
| from autotest_lib.client.cros.audio import audio_helper |
| from autotest_lib.client.cros.chameleon import audio_widget |
| from autotest_lib.client.cros.chameleon import audio_widget_link |
| from autotest_lib.client.cros.chameleon import chameleon_audio_ids as ids |
| |
| |
| class AudioPort(object): |
| """ |
| This class abstracts an audio port in audio test framework. A port is |
| identified by its host and interface. Available hosts and interfaces |
| are listed in chameleon_audio_ids. |
| |
| Properties: |
| port_id: The port id defined in chameleon_audio_ids. |
| host: The host of this audio port, e.g. 'Chameleon', 'Cros', |
| 'Peripheral'. |
| interface: The interface of this audio port, e.g. 'HDMI', 'Headphone'. |
| direction: The direction of this audio port, that is, 'Input' or |
| 'Output'. Note that bidirectional interface like 3.5mm |
| jack is separated to two interfaces 'Headphone' and |
| 'External Mic'. |
| |
| """ |
| def __init__(self, port_id): |
| """Initialize an AudioPort with port id string. |
| |
| @param port_id: A port id string defined in chameleon_audio_ids. |
| |
| """ |
| logging.debug('Creating AudioPort with port_id: %s', port_id) |
| self.port_id = port_id |
| self.host = ids.get_host(port_id) |
| self.interface = ids.get_interface(port_id) |
| self.direction = ids.get_direction(port_id) |
| logging.debug('Created AudioPort: %s', self) |
| |
| |
| def __str__(self): |
| """String representation of audio port. |
| |
| @returns: The string representation of audio port which is composed by |
| host, interface, and direction. |
| |
| """ |
| return '( %s | %s | %s )' % (self.host, self.interface, self.direction) |
| |
| |
| class AudioLinkFactoryError(Exception): |
| """Error in AudioLinkFactory.""" |
| pass |
| |
| |
| class AudioLinkFactory(object): |
| """ |
| This class provides method to create link that connects widgets. |
| This is used by AudioWidgetFactory when user wants to create binder for |
| widgets. |
| |
| Properties: |
| _audio_buses: A dict containing mapping from index number |
| to object of AudioBusLink's subclass. |
| |
| """ |
| |
| # Maps pair of widgets to widget link of different type. |
| LINK_TABLE = { |
| (ids.CrosIds.HDMI, ids.ChameleonIds.HDMI): |
| audio_widget_link.HDMIWidgetLink, |
| (ids.CrosIds.HEADPHONE, ids.ChameleonIds.LINEIN): |
| audio_widget_link.AudioBusToChameleonLink, |
| # TODO(cychiang): Add link for other widget pairs. |
| } |
| |
| def __init__(self): |
| # There are two audio buses on audio board. Initializes them to |
| # None. They may be changed to objects of AudioBusLink's subclass. |
| self._audio_buses = {0: None, 1: None} |
| |
| |
| def _acquire_audio_bus_index(self): |
| """Acquires an available audio bus index that is not occupied yet. |
| |
| @returns: A number. |
| |
| @raises: AudioLinkFactoryError if there is no available |
| audio bus. |
| """ |
| for index, bus in self._audio_buses.iteritems(): |
| if not (bus and bus.occupied): |
| return index |
| |
| raise AudioLinkFactoryError('No available audio bus') |
| |
| |
| def create_link(self, source, sink): |
| """Creates a widget link for two audio widgets. |
| |
| @param source: An AudioWidget. |
| @param sink: An AudioWidget. |
| |
| @returns: An object of WidgetLink's subclass. |
| |
| @raises: AudioLinkFactoryError if there is no link between |
| source and sink. |
| |
| """ |
| # Finds the available link types from LINK_TABLE. |
| link_type = self.LINK_TABLE.get((source.port_id, sink.port_id), None) |
| if not link_type: |
| raise AudioLinkFactoryError( |
| 'No supported link between %s and %s' % ( |
| source.pord_id, sink.port_id)) |
| |
| # There is only one dedicated HDMI cable, just use it. |
| if link_type == audio_widget_link.HDMIWidgetLink: |
| link = audio_widget_link.HDMIWidgetLink() |
| |
| # Acquires audio bus if there is available bus. |
| # Creates a bus of AudioBusLink's subclass that is more |
| # specific than AudioBusLink. |
| elif issubclass(link_type, audio_widget_link.AudioBusLink): |
| bus_index = self._acquire_audio_bus_index() |
| link = link_type(bus_index) |
| self._audio_buses[bus_index] = link |
| else: |
| raise NotImplementedError('Link %s is not implemented' % link_type) |
| |
| return link |
| |
| |
| class AudioWidgetFactory(object): |
| """ |
| This class provides methods to create widgets and binder of widgets. |
| User can use binder to setup audio paths. User can use widgets to control |
| record/playback on different ports on Cros device or Chameleon. |
| |
| Properties: |
| _audio_facade: An AudioFacadeRemoteAdapter to access Cros device audio |
| functionality. This is created by the |
| 'factory' argument passed to the constructor. |
| _chameleon_board: A ChameleonBoard object to access Chameleon |
| functionality. |
| _link_factory: An AudioLinkFactory that creates link for widgets. |
| |
| """ |
| def __init__(self, chameleon_board, factory): |
| """Initializes a AudioWidgetFactory |
| |
| @param chameleon_board: A ChameleonBoard object to access Chameleon |
| functionality. |
| @param factory: A facade factory to access Cros device functionality. |
| Currently only audio facade is used, but we can access |
| other functionalities including display and video by |
| facades created by this facade factory. |
| |
| """ |
| self._audio_facade = factory.create_audio_facade() |
| self._chameleon_board = chameleon_board |
| self._link_factory = AudioLinkFactory() |
| |
| |
| def create_widget(self, port_id): |
| """Creates a AudioWidget given port id string. |
| |
| @param port_id: A port id string defined in chameleon_audio_ids. |
| |
| @returns: An AudioWidget that is actually a |
| (Chameleon/Cros/Peripheral)(Input/Output)Widget. |
| |
| """ |
| def _create_chameleon_handler(audio_port): |
| """Creates a ChameleonWidgetHandler for a given AudioPort. |
| |
| @param audio_port: An AudioPort object. |
| |
| @returns: A Chameleon(Input/Output)WidgetHandler depending on |
| direction of audio_port. |
| |
| """ |
| if audio_port.direction == 'Input': |
| return audio_widget.ChameleonInputWidgetHandler( |
| self._chameleon_board, audio_port.interface) |
| else: |
| return audio_widget.ChameleonOutputWidgetHandler( |
| self._chameleon_board, audio_port.interface) |
| |
| |
| def _create_cros_handler(audio_port): |
| """Creates a CrosWidgetHandler for a given AudioPort. |
| |
| @param audio_port: An AudioPort object. |
| |
| @returns: A Cros(Input/Output)WidgetHandler depending on |
| direction of audio_port. |
| |
| """ |
| if audio_port.direction == 'Input': |
| return audio_widget.CrosInputWidgetHandler(self._audio_facade) |
| else: |
| return audio_widget.CrosOutputWidgetHandler(self._audio_facade) |
| |
| |
| def _create_audio_widget(audio_port, handler): |
| """Creates an AudioWidget for given AudioPort using WidgetHandler. |
| |
| Creates an AudioWidget with the direction of audio_port. Put |
| the widget handler into the widget so the widget can handle |
| action requests. |
| |
| @param audio_port: An AudioPort object. |
| @param handler: A WidgetHandler object. |
| |
| @returns: An Audio(Input/Output)Widget depending on |
| direction of audio_port. |
| |
| """ |
| if audio_port.direction == 'Input': |
| return audio_widget.AudioInputWidget(audio_port, handler) |
| return audio_widget.AudioOutputWidget(audio_port, handler) |
| |
| |
| audio_port = AudioPort(port_id) |
| if audio_port.host == 'Chameleon': |
| handler = _create_chameleon_handler(audio_port) |
| elif audio_port.host == 'Cros': |
| handler = _create_cros_handler(audio_port) |
| elif audio_port.host == 'Peripheral': |
| handler = audio_widget.PeripheralWidgetHandler() |
| |
| return _create_audio_widget(audio_port, handler) |
| |
| |
| def create_binder(self, source, sink): |
| """Creates a WidgetBinder for two AudioWidgets. |
| |
| @param source: An AudioWidget. |
| @param sink: An AudioWidget. |
| |
| @returns: A WidgetBinder object. |
| |
| """ |
| return audio_widget_link.WidgetBinder( |
| source, self._link_factory.create_link(source, sink), sink) |
| |
| |
| def compare_recorded_result(golden_file, recorder, method): |
| """Check recoded audio in a AudioInputWidget against a golden file. |
| |
| Compares recorded data with golden data by cross correlation method. |
| Refer to audio_helper.compare_data for details of comparison. |
| |
| @param golden_file: An AudioTestData object that serves as golden data. |
| @param recorder: An AudioInputWidget that has recorded some audio data. |
| @param method: The method to compare recorded result. Currently, |
| 'correlation' and 'frequency' are supported. |
| |
| @returns: True if the recorded data and golden data are similar enough. |
| |
| """ |
| logging.info('Comparing recorded data with golden file %s ...', |
| golden_file.path) |
| return audio_helper.compare_data( |
| golden_file.get_binary(), golden_file.data_format, |
| recorder.get_binary(), recorder.data_format, recorder.channel_map, |
| method) |
| |
| |
| @contextmanager |
| def bind_widgets(binder): |
| """Context manager for widget binders. |
| |
| Connects widgets in the beginning. Disconnects widgets and releases binder |
| in the end. |
| |
| @param binder: A WidgetBinder object. |
| |
| E.g. with bind_widgets(binder): |
| do something on widget. |
| |
| """ |
| binder.connect() |
| yield |
| binder.disconnect() |
| binder.release() |