chameleon_audio_helper.py revision 17a2527ea44ed461cec2cf62577079f59e00a9b3
1# Copyright 2014 The Chromium OS Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5"""This module provides the framework for audio tests using chameleon.""" 6 7import logging 8from contextlib import contextmanager 9 10from autotest_lib.client.cros.audio import audio_helper 11from autotest_lib.client.cros.chameleon import audio_widget 12from autotest_lib.client.cros.chameleon import audio_widget_link 13from autotest_lib.client.cros.chameleon import chameleon_audio_ids as ids 14 15 16class AudioPort(object): 17 """ 18 This class abstracts an audio port in audio test framework. A port is 19 identified by its host and interface. Available hosts and interfaces 20 are listed in chameleon_audio_ids. 21 22 Properties: 23 port_id: The port id defined in chameleon_audio_ids. 24 host: The host of this audio port, e.g. 'Chameleon', 'Cros', 25 'Peripheral'. 26 interface: The interface of this audio port, e.g. 'HDMI', 'Headphone'. 27 direction: The direction of this audio port, that is, 'Input' or 28 'Output'. Note that bidirectional interface like 3.5mm 29 jack is separated to two interfaces 'Headphone' and 30 'External Mic'. 31 32 """ 33 def __init__(self, port_id): 34 """Initialize an AudioPort with port id string. 35 36 @param port_id: A port id string defined in chameleon_audio_ids. 37 38 """ 39 logging.debug('Creating AudioPort with port_id: %s', port_id) 40 self.port_id = port_id 41 self.host = ids.get_host(port_id) 42 self.interface = ids.get_interface(port_id) 43 self.direction = ids.get_direction(port_id) 44 logging.debug('Created AudioPort: %s', self) 45 46 47 def __str__(self): 48 """String representation of audio port. 49 50 @returns: The string representation of audio port which is composed by 51 host, interface, and direction. 52 53 """ 54 return '( %s | %s | %s )' % (self.host, self.interface, self.direction) 55 56 57class AudioLinkFactoryError(Exception): 58 """Error in AudioLinkFactory.""" 59 pass 60 61 62class AudioLinkFactory(object): 63 """ 64 This class provides method to create link that connects widgets. 65 This is used by AudioWidgetFactory when user wants to create binder for 66 widgets. 67 68 Properties: 69 _audio_buses: A dict containing mapping from index number 70 to object of AudioBusLink's subclass. 71 72 """ 73 74 # Maps pair of widgets to widget link of different type. 75 LINK_TABLE = { 76 (ids.CrosIds.HDMI, ids.ChameleonIds.HDMI): 77 audio_widget_link.HDMIWidgetLink, 78 (ids.CrosIds.HEADPHONE, ids.ChameleonIds.LINEIN): 79 audio_widget_link.AudioBusToChameleonLink, 80 # TODO(cychiang): Add link for other widget pairs. 81 } 82 83 def __init__(self): 84 # There are two audio buses on audio board. Initializes them to 85 # None. They may be changed to objects of AudioBusLink's subclass. 86 self._audio_buses = {0: None, 1: None} 87 88 89 def _acquire_audio_bus_index(self): 90 """Acquires an available audio bus index that is not occupied yet. 91 92 @returns: A number. 93 94 @raises: AudioLinkFactoryError if there is no available 95 audio bus. 96 """ 97 for index, bus in self._audio_buses.iteritems(): 98 if not (bus and bus.occupied): 99 return index 100 101 raise AudioLinkFactoryError('No available audio bus') 102 103 104 def create_link(self, source, sink): 105 """Creates a widget link for two audio widgets. 106 107 @param source: An AudioWidget. 108 @param sink: An AudioWidget. 109 110 @returns: An object of WidgetLink's subclass. 111 112 @raises: AudioLinkFactoryError if there is no link between 113 source and sink. 114 115 """ 116 # Finds the available link types from LINK_TABLE. 117 link_type = self.LINK_TABLE.get((source.port_id, sink.port_id), None) 118 if not link_type: 119 raise AudioLinkFactoryError( 120 'No supported link between %s and %s' % ( 121 source.pord_id, sink.port_id)) 122 123 # There is only one dedicated HDMI cable, just use it. 124 if link_type == audio_widget_link.HDMIWidgetLink: 125 link = audio_widget_link.HDMIWidgetLink() 126 127 # Acquires audio bus if there is available bus. 128 # Creates a bus of AudioBusLink's subclass that is more 129 # specific than AudioBusLink. 130 elif issubclass(link_type, audio_widget_link.AudioBusLink): 131 bus_index = self._acquire_audio_bus_index() 132 link = link_type(bus_index) 133 self._audio_buses[bus_index] = link 134 else: 135 raise NotImplementedError('Link %s is not implemented' % link_type) 136 137 return link 138 139 140class AudioWidgetFactory(object): 141 """ 142 This class provides methods to create widgets and binder of widgets. 143 User can use binder to setup audio paths. User can use widgets to control 144 record/playback on different ports on Cros device or Chameleon. 145 146 Properties: 147 _audio_facade: An AudioFacadeRemoteAdapter to access Cros device audio 148 functionality. This is created by the 149 'factory' argument passed to the constructor. 150 _chameleon_board: A ChameleonBoard object to access Chameleon 151 functionality. 152 _link_factory: An AudioLinkFactory that creates link for widgets. 153 154 """ 155 def __init__(self, chameleon_board, factory): 156 """Initializes a AudioWidgetFactory 157 158 @param chameleon_board: A ChameleonBoard object to access Chameleon 159 functionality. 160 @param factory: A facade factory to access Cros device functionality. 161 Currently only audio facade is used, but we can access 162 other functionalities including display and video by 163 facades created by this facade factory. 164 165 """ 166 self._audio_facade = factory.create_audio_facade() 167 self._chameleon_board = chameleon_board 168 self._link_factory = AudioLinkFactory() 169 170 171 def create_widget(self, port_id): 172 """Creates a AudioWidget given port id string. 173 174 @param port_id: A port id string defined in chameleon_audio_ids. 175 176 @returns: An AudioWidget that is actually a 177 (Chameleon/Cros/Peripheral)(Input/Output)Widget. 178 179 """ 180 def _create_chameleon_handler(audio_port): 181 """Creates a ChameleonWidgetHandler for a given AudioPort. 182 183 @param audio_port: An AudioPort object. 184 185 @returns: A Chameleon(Input/Output)WidgetHandler depending on 186 direction of audio_port. 187 188 """ 189 if audio_port.direction == 'Input': 190 return audio_widget.ChameleonInputWidgetHandler( 191 self._chameleon_board, audio_port.interface) 192 else: 193 return audio_widget.ChameleonOutputWidgetHandler( 194 self._chameleon_board, audio_port.interface) 195 196 197 def _create_cros_handler(audio_port): 198 """Creates a CrosWidgetHandler for a given AudioPort. 199 200 @param audio_port: An AudioPort object. 201 202 @returns: A Cros(Input/Output)WidgetHandler depending on 203 direction of audio_port. 204 205 """ 206 if audio_port.direction == 'Input': 207 return audio_widget.CrosInputWidgetHandler(self._audio_facade) 208 else: 209 return audio_widget.CrosOutputWidgetHandler(self._audio_facade) 210 211 212 def _create_audio_widget(audio_port, handler): 213 """Creates an AudioWidget for given AudioPort using WidgetHandler. 214 215 Creates an AudioWidget with the direction of audio_port. Put 216 the widget handler into the widget so the widget can handle 217 action requests. 218 219 @param audio_port: An AudioPort object. 220 @param handler: A WidgetHandler object. 221 222 @returns: An Audio(Input/Output)Widget depending on 223 direction of audio_port. 224 225 """ 226 if audio_port.direction == 'Input': 227 return audio_widget.AudioInputWidget(audio_port, handler) 228 return audio_widget.AudioOutputWidget(audio_port, handler) 229 230 231 audio_port = AudioPort(port_id) 232 if audio_port.host == 'Chameleon': 233 handler = _create_chameleon_handler(audio_port) 234 elif audio_port.host == 'Cros': 235 handler = _create_cros_handler(audio_port) 236 elif audio_port.host == 'Peripheral': 237 handler = audio_widget.PeripheralWidgetHandler() 238 239 return _create_audio_widget(audio_port, handler) 240 241 242 def create_binder(self, source, sink): 243 """Creates a WidgetBinder for two AudioWidgets. 244 245 @param source: An AudioWidget. 246 @param sink: An AudioWidget. 247 248 @returns: A WidgetBinder object. 249 250 """ 251 return audio_widget_link.WidgetBinder( 252 source, self._link_factory.create_link(source, sink), sink) 253 254 255def compare_recorded_result(golden_file, recorder, method): 256 """Check recoded audio in a AudioInputWidget against a golden file. 257 258 Compares recorded data with golden data by cross correlation method. 259 Refer to audio_helper.compare_data for details of comparison. 260 261 @param golden_file: An AudioTestData object that serves as golden data. 262 @param recorder: An AudioInputWidget that has recorded some audio data. 263 @param method: The method to compare recorded result. Currently, 264 'correlation' and 'frequency' are supported. 265 266 @returns: True if the recorded data and golden data are similar enough. 267 268 """ 269 logging.info('Comparing recorded data with golden file %s ...', 270 golden_file.path) 271 return audio_helper.compare_data( 272 golden_file.get_binary(), golden_file.data_format, 273 recorder.get_binary(), recorder.data_format, recorder.channel_map, 274 method) 275 276 277@contextmanager 278def bind_widgets(binder): 279 """Context manager for widget binders. 280 281 Connects widgets in the beginning. Disconnects widgets and releases binder 282 in the end. 283 284 @param binder: A WidgetBinder object. 285 286 E.g. with bind_widgets(binder): 287 do something on widget. 288 289 """ 290 binder.connect() 291 yield 292 binder.disconnect() 293 binder.release() 294