Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 1 | # Copyright (c) 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 | """Chameleond Driver for FPGA customized platform with the TIO card.""" |
| 5 | |
Tom Wai-Hong Tam | ba7451d | 2014-10-16 06:44:17 +0800 | [diff] [blame] | 6 | import functools |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 7 | import logging |
| 8 | import os |
| 9 | import xmlrpclib |
| 10 | |
| 11 | import chameleon_common # pylint: disable=W0611 |
| 12 | from chameleond.interface import ChameleondInterface |
Yuan | 3342be7 | 2014-07-21 18:12:00 +0800 | [diff] [blame] | 13 | |
Cheng-Yi Chiang | 32cbd45 | 2015-02-22 17:54:35 +0800 | [diff] [blame] | 14 | from chameleond.utils import audio_board |
Cheng-Yi Chiang | 2f53ea2 | 2014-10-01 14:58:27 +0800 | [diff] [blame] | 15 | from chameleond.utils import codec_flow |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 16 | from chameleond.utils import fpga |
Tom Wai-Hong Tam | f7f6e3c | 2014-10-15 04:53:52 +0800 | [diff] [blame] | 17 | from chameleond.utils import i2c |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 18 | from chameleond.utils import ids |
| 19 | from chameleond.utils import input_flow |
| 20 | |
| 21 | |
| 22 | class DriverError(Exception): |
| 23 | """Exception raised when any error on FPGA driver.""" |
| 24 | pass |
| 25 | |
| 26 | |
Cheng-Yi Chiang | 9e62a86 | 2014-10-09 19:16:01 +0800 | [diff] [blame] | 27 | def _AudioMethod(input_only=False, output_only=False): |
| 28 | """Decorator that checks the port_id argument is an audio port. |
| 29 | |
| 30 | Args: |
| 31 | input_only: True to check if port is an input port. |
| 32 | output_only: True to check if port is an output port. |
| 33 | """ |
| 34 | def _ActualDecorator(func): |
| 35 | @functools.wraps(func) |
| 36 | def wrapper(instance, port_id, *args, **kwargs): |
| 37 | if not ids.IsAudioPort(port_id): |
| 38 | raise DriverError( |
| 39 | 'Not a valid port_id for audio operation: %d' % port_id) |
| 40 | if input_only and not ids.IsInputPort(port_id): |
| 41 | raise DriverError( |
| 42 | 'Not a valid port_id for input operation: %d' % port_id) |
| 43 | elif output_only and not ids.IsOutputPort(port_id): |
| 44 | raise DriverError( |
| 45 | 'Not a valid port_id for output operation: %d' % port_id) |
| 46 | return func(instance, port_id, *args, **kwargs) |
| 47 | return wrapper |
| 48 | return _ActualDecorator |
Tom Wai-Hong Tam | ba7451d | 2014-10-16 06:44:17 +0800 | [diff] [blame] | 49 | |
| 50 | |
| 51 | def _VideoMethod(func): |
| 52 | """Decorator that checks the port_id argument is a video port.""" |
| 53 | @functools.wraps(func) |
| 54 | def wrapper(instance, port_id, *args, **kwargs): |
| 55 | if not ids.IsVideoPort(port_id): |
| 56 | raise DriverError('Not a valid port_id for video operation: %d' % port_id) |
| 57 | return func(instance, port_id, *args, **kwargs) |
| 58 | return wrapper |
| 59 | |
| 60 | |
Cheng-Yi Chiang | 32cbd45 | 2015-02-22 17:54:35 +0800 | [diff] [blame] | 61 | def _AudioBoardMethod(func): |
| 62 | """Decorator that checks there is an audio board.""" |
| 63 | @functools.wraps(func) |
| 64 | def wrapper(instance, *args, **kwargs): |
| 65 | if not instance.HasAudioBoard(): |
| 66 | raise DriverError('There is no audio board') |
| 67 | return func(instance, *args, **kwargs) |
| 68 | return wrapper |
| 69 | |
| 70 | |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 71 | class ChameleondDriver(ChameleondInterface): |
| 72 | """Chameleond Driver for FPGA customized platform.""" |
| 73 | |
| 74 | _I2C_BUS_MAIN = 0 |
Cheng-Yi Chiang | 433d984 | 2015-02-22 16:52:35 +0800 | [diff] [blame] | 75 | _I2C_BUS_AUDIO_CODEC = 1 |
Cheng-Yi Chiang | 32cbd45 | 2015-02-22 17:54:35 +0800 | [diff] [blame] | 76 | _I2C_BUS_AUDIO_BOARD = 3 |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 77 | |
Tom Wai-Hong Tam | 32ce343 | 2014-06-12 09:17:43 +0800 | [diff] [blame] | 78 | # Time to wait for video frame dump to start before a timeout error is raised |
Tom Wai-Hong Tam | 268def6 | 2014-07-09 07:45:33 +0800 | [diff] [blame] | 79 | _TIMEOUT_FRAME_DUMP_PROBE = 60.0 |
Tom Wai-Hong Tam | 32ce343 | 2014-06-12 09:17:43 +0800 | [diff] [blame] | 80 | |
Tom Wai-Hong Tam | f2b1a20 | 2014-06-17 03:04:34 +0800 | [diff] [blame] | 81 | # The frame index which is used for the regular DumpPixels API. |
| 82 | _DEFAULT_FRAME_INDEX = 0 |
Tom Wai-Hong Tam | 9ab344a | 2014-06-17 03:17:36 +0800 | [diff] [blame] | 83 | _DEFAULT_FRAME_LIMIT = _DEFAULT_FRAME_INDEX + 1 |
Tom Wai-Hong Tam | f2b1a20 | 2014-06-17 03:04:34 +0800 | [diff] [blame] | 84 | |
Tom Wai-Hong Tam | 12609b2 | 2014-08-01 01:36:24 +0800 | [diff] [blame] | 85 | # Limit the period of async capture to 3min (in 60fps). |
| 86 | _MAX_CAPTURED_FRAME_COUNT = 3 * 60 * 60 |
| 87 | |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 88 | def __init__(self, *args, **kwargs): |
| 89 | super(ChameleondDriver, self).__init__(*args, **kwargs) |
| 90 | self._selected_input = None |
Cheng-Yi Chiang | 9e62a86 | 2014-10-09 19:16:01 +0800 | [diff] [blame] | 91 | self._selected_output = None |
Tom Wai-Hong Tam | f2b1a20 | 2014-06-17 03:04:34 +0800 | [diff] [blame] | 92 | self._captured_params = {} |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 93 | # Reserve index 0 as the default EDID. |
| 94 | self._all_edids = [self._ReadDefaultEdid()] |
| 95 | |
| 96 | main_bus = i2c.I2cBus(self._I2C_BUS_MAIN) |
Cheng-Yi Chiang | 433d984 | 2015-02-22 16:52:35 +0800 | [diff] [blame] | 97 | audio_codec_bus = i2c.I2cBus(self._I2C_BUS_AUDIO_CODEC) |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 98 | fpga_ctrl = fpga.FpgaController() |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 99 | self._flows = { |
Tom Wai-Hong Tam | 0d12884 | 2015-01-14 07:13:45 +0800 | [diff] [blame] | 100 | ids.DP1: input_flow.DpInputFlow(ids.DP1, main_bus, fpga_ctrl), |
| 101 | ids.DP2: input_flow.DpInputFlow(ids.DP2, main_bus, fpga_ctrl), |
| 102 | ids.HDMI: input_flow.HdmiInputFlow(ids.HDMI, main_bus, fpga_ctrl), |
| 103 | ids.VGA: input_flow.VgaInputFlow(ids.VGA, main_bus, fpga_ctrl), |
Cheng-Yi Chiang | 433d984 | 2015-02-22 16:52:35 +0800 | [diff] [blame] | 104 | ids.MIC: codec_flow.InputCodecFlow(ids.MIC, audio_codec_bus, fpga_ctrl), |
| 105 | ids.LINEIN: codec_flow.InputCodecFlow(ids.LINEIN, audio_codec_bus, |
| 106 | fpga_ctrl), |
Tom Wai-Hong Tam | 0d12884 | 2015-01-14 07:13:45 +0800 | [diff] [blame] | 107 | ids.LINEOUT: codec_flow.OutputCodecFlow( |
Cheng-Yi Chiang | 433d984 | 2015-02-22 16:52:35 +0800 | [diff] [blame] | 108 | ids.LINEOUT, audio_codec_bus, fpga_ctrl) |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 109 | } |
| 110 | |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 111 | for flow in self._flows.itervalues(): |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 112 | if flow: |
| 113 | flow.Initialize() |
| 114 | |
Cheng-Yi Chiang | 32cbd45 | 2015-02-22 17:54:35 +0800 | [diff] [blame] | 115 | # Some Chameleon might not have audio board installed. |
| 116 | self._audio_board = None |
| 117 | try: |
| 118 | audio_board_bus = i2c.I2cBus(self._I2C_BUS_AUDIO_BOARD) |
| 119 | self._audio_board = audio_board.AudioBoard(audio_board_bus) |
| 120 | except audio_board.AudioBoardException: |
| 121 | logging.warning('There is no audio board on this Chameleon') |
| 122 | else: |
| 123 | logging.info('There is an audio board on this Chameleon') |
| 124 | |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 125 | self.Reset() |
| 126 | |
| 127 | # Set all ports unplugged on initialization. |
Tom Wai-Hong Tam | 0fca92b | 2015-01-15 02:13:55 +0800 | [diff] [blame] | 128 | for port_id in self.GetSupportedPorts(): |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 129 | self.Unplug(port_id) |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 130 | |
| 131 | def Reset(self): |
| 132 | """Resets Chameleon board.""" |
Tom Wai-Hong Tam | 6b0b388 | 2014-12-17 04:09:29 +0800 | [diff] [blame] | 133 | logging.info('Execute the reset process') |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 134 | # TODO(waihong): Add other reset routines. |
Tom Wai-Hong Tam | ae0907d | 2015-01-14 09:29:50 +0800 | [diff] [blame] | 135 | logging.info('Apply the default EDID and enable DDC on all video inputs') |
| 136 | for port_id in self.GetSupportedInputs(): |
| 137 | if self.HasVideoSupport(port_id): |
| 138 | self.ApplyEdid(port_id, ids.EDID_ID_DEFAULT) |
| 139 | self.SetDdcState(port_id, enabled=True) |
Cheng-Yi Chiang | b8b247e | 2015-01-20 11:41:51 +0800 | [diff] [blame] | 140 | for port_id in self.GetSupportedPorts(): |
| 141 | if self.HasAudioSupport(port_id): |
| 142 | self._flows[port_id].ResetRoute() |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 143 | |
Cheng-Yi Chiang | 32cbd45 | 2015-02-22 17:54:35 +0800 | [diff] [blame] | 144 | if self.HasAudioBoard(): |
| 145 | self._audio_board.Reset() |
| 146 | |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 147 | def GetSupportedPorts(self): |
| 148 | """Returns all supported ports on the board. |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 149 | |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 150 | Not like the ProbePorts() method which only returns the ports which |
| 151 | are connected, this method returns all supported ports on the board. |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 152 | |
| 153 | Returns: |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 154 | A tuple of port_id, for all supported ports on the board. |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 155 | """ |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 156 | return self._flows.keys() |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 157 | |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 158 | def GetSupportedInputs(self): |
| 159 | """Returns all supported input ports on the board. |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 160 | |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 161 | Not like the ProbeInputs() method which only returns the input ports which |
| 162 | are connected, this method returns all supported input ports on the board. |
| 163 | |
| 164 | Returns: |
| 165 | A tuple of port_id, for all supported input port on the board. |
| 166 | """ |
Tom Wai-Hong Tam | ba7451d | 2014-10-16 06:44:17 +0800 | [diff] [blame] | 167 | return ids.INPUT_PORTS |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 168 | |
| 169 | def GetSupportedOutputs(self): |
| 170 | """Returns all supported output ports on the board. |
| 171 | |
| 172 | Not like the ProbeOutputs() method which only returns the output ports which |
| 173 | are connected, this method returns all supported output ports on the board. |
| 174 | |
| 175 | Returns: |
| 176 | A tuple of port_id, for all supported output port on the board. |
| 177 | """ |
Tom Wai-Hong Tam | ba7451d | 2014-10-16 06:44:17 +0800 | [diff] [blame] | 178 | return ids.OUTPUT_PORTS |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 179 | |
| 180 | def IsPhysicalPlugged(self, port_id): |
| 181 | """Returns true if the physical cable is plugged between DUT and Chameleon. |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 182 | |
| 183 | Args: |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 184 | port_id: The ID of the input/output port. |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 185 | |
| 186 | Returns: |
| 187 | True if the physical cable is plugged; otherwise, False. |
| 188 | """ |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 189 | return self._flows[port_id].IsPhysicalPlugged() |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 190 | |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 191 | def ProbePorts(self): |
| 192 | """Probes all the connected ports on Chameleon board. |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 193 | |
| 194 | Returns: |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 195 | A tuple of port_id, for the ports connected to DUT. |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 196 | """ |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 197 | return tuple(port_id for port_id in self.GetSupportedPorts() |
| 198 | if self.IsPhysicalPlugged(port_id)) |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 199 | |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 200 | def ProbeInputs(self): |
| 201 | """Probes all the connected input ports on Chameleon board. |
| 202 | |
| 203 | Returns: |
| 204 | A tuple of port_id, for the input ports connected to DUT. |
| 205 | """ |
| 206 | return tuple(port_id for port_id in self.GetSupportedInputs() |
| 207 | if self.IsPhysicalPlugged(port_id)) |
| 208 | |
| 209 | def ProbeOutputs(self): |
| 210 | """Probes all the connected output ports on Chameleon board. |
| 211 | |
| 212 | Returns: |
| 213 | A tuple of port_id, for the output ports connected to DUT. |
| 214 | """ |
| 215 | return tuple(port_id for port_id in self.GetSupportedOutputs() |
| 216 | if self.IsPhysicalPlugged(port_id)) |
| 217 | |
| 218 | def GetConnectorType(self, port_id): |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 219 | """Returns the human readable string for the connector type. |
| 220 | |
| 221 | Args: |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 222 | port_id: The ID of the input/output port. |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 223 | |
| 224 | Returns: |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 225 | A string, like "HDMI", "DP", "MIC", etc. |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 226 | """ |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 227 | return self._flows[port_id].GetConnectorType() |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 228 | |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 229 | def HasAudioSupport(self, port_id): |
| 230 | """Returns true if the port has audio support. |
Tom Wai-Hong Tam | 1b17ade | 2014-10-14 07:59:43 +0800 | [diff] [blame] | 231 | |
| 232 | Args: |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 233 | port_id: The ID of the input/output port. |
Tom Wai-Hong Tam | 1b17ade | 2014-10-14 07:59:43 +0800 | [diff] [blame] | 234 | |
| 235 | Returns: |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 236 | True if the input/output port has audio support; otherwise, False. |
Tom Wai-Hong Tam | 1b17ade | 2014-10-14 07:59:43 +0800 | [diff] [blame] | 237 | """ |
Tom Wai-Hong Tam | ba7451d | 2014-10-16 06:44:17 +0800 | [diff] [blame] | 238 | return ids.IsAudioPort(port_id) |
Tom Wai-Hong Tam | 1b17ade | 2014-10-14 07:59:43 +0800 | [diff] [blame] | 239 | |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 240 | def HasVideoSupport(self, port_id): |
| 241 | """Returns true if the port has video support. |
Tom Wai-Hong Tam | 1b17ade | 2014-10-14 07:59:43 +0800 | [diff] [blame] | 242 | |
| 243 | Args: |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 244 | port_id: The ID of the input/output port. |
Tom Wai-Hong Tam | 1b17ade | 2014-10-14 07:59:43 +0800 | [diff] [blame] | 245 | |
| 246 | Returns: |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 247 | True if the input/output port has video support; otherwise, False. |
Tom Wai-Hong Tam | 1b17ade | 2014-10-14 07:59:43 +0800 | [diff] [blame] | 248 | """ |
Tom Wai-Hong Tam | ba7451d | 2014-10-16 06:44:17 +0800 | [diff] [blame] | 249 | return ids.IsVideoPort(port_id) |
Tom Wai-Hong Tam | 1b17ade | 2014-10-14 07:59:43 +0800 | [diff] [blame] | 250 | |
Tom Wai-Hong Tam | ba7451d | 2014-10-16 06:44:17 +0800 | [diff] [blame] | 251 | @_VideoMethod |
Tom Wai-Hong Tam | 26009b1 | 2014-11-06 07:02:40 +0800 | [diff] [blame] | 252 | def SetVgaMode(self, port_id, mode): |
| 253 | """Sets the mode for VGA monitor. |
| 254 | |
| 255 | Args: |
| 256 | port_id: The ID of the VGA port. |
Tom Wai-Hong Tam | 4541141 | 2014-11-11 04:07:36 +0800 | [diff] [blame] | 257 | mode: A string of the mode name, e.g. 'PC_1920x1080x60'. Use 'auto' |
| 258 | to detect the VGA mode automatically. |
Tom Wai-Hong Tam | 26009b1 | 2014-11-06 07:02:40 +0800 | [diff] [blame] | 259 | """ |
| 260 | if port_id == ids.VGA: |
Tom Wai-Hong Tam | 6b0b388 | 2014-12-17 04:09:29 +0800 | [diff] [blame] | 261 | logging.info('Set VGA port #%d to mode: %s', port_id, mode) |
Tom Wai-Hong Tam | 26009b1 | 2014-11-06 07:02:40 +0800 | [diff] [blame] | 262 | self._flows[port_id].SetVgaMode(mode) |
| 263 | else: |
| 264 | raise DriverError('SetVgaMode only works on VGA port.') |
| 265 | |
| 266 | @_VideoMethod |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 267 | def WaitVideoInputStable(self, port_id, timeout=None): |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 268 | """Waits the video input stable or timeout. |
| 269 | |
| 270 | Args: |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 271 | port_id: The ID of the video input port. |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 272 | timeout: The time period to wait for. |
| 273 | |
| 274 | Returns: |
| 275 | True if the video input becomes stable within the timeout period; |
| 276 | otherwise, False. |
| 277 | """ |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 278 | return self._flows[port_id].WaitVideoInputStable(timeout) |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 279 | |
| 280 | def _ReadDefaultEdid(self): |
| 281 | """Reads the default EDID from file. |
| 282 | |
| 283 | Returns: |
| 284 | A byte array of EDID data. |
| 285 | """ |
| 286 | driver_dir = os.path.dirname(os.path.realpath(__file__)) |
| 287 | edid_path = os.path.join(driver_dir, '..', 'data', 'default_edid.bin') |
| 288 | return open(edid_path).read() |
| 289 | |
| 290 | def CreateEdid(self, edid): |
| 291 | """Creates an internal record of EDID using the given byte array. |
| 292 | |
| 293 | Args: |
| 294 | edid: A byte array of EDID data, wrapped in a xmlrpclib.Binary object. |
| 295 | |
| 296 | Returns: |
| 297 | An edid_id. |
| 298 | """ |
| 299 | if None in self._all_edids: |
| 300 | last = self._all_edids.index(None) |
| 301 | self._all_edids[last] = edid.data |
| 302 | else: |
| 303 | last = len(self._all_edids) |
| 304 | self._all_edids.append(edid.data) |
| 305 | return last |
| 306 | |
| 307 | def DestroyEdid(self, edid_id): |
| 308 | """Destroys the internal record of EDID. The internal data will be freed. |
| 309 | |
| 310 | Args: |
| 311 | edid_id: The ID of the EDID, which was created by CreateEdid(). |
| 312 | """ |
Tom Wai-Hong Tam | c78b9eb | 2014-12-18 07:49:51 +0800 | [diff] [blame] | 313 | if edid_id > ids.EDID_ID_DEFAULT: |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 314 | self._all_edids[edid_id] = None |
| 315 | else: |
| 316 | raise DriverError('Not a valid edid_id.') |
| 317 | |
Tom Wai-Hong Tam | ba7451d | 2014-10-16 06:44:17 +0800 | [diff] [blame] | 318 | @_VideoMethod |
Tom Wai-Hong Tam | ae0907d | 2015-01-14 09:29:50 +0800 | [diff] [blame] | 319 | def SetDdcState(self, port_id, enabled): |
| 320 | """Sets the enabled/disabled state of DDC bus on the given video input. |
| 321 | |
| 322 | Args: |
| 323 | port_id: The ID of the video input port. |
| 324 | enabled: True to enable DDC bus due to an user request; False to |
| 325 | disable it. |
| 326 | """ |
| 327 | logging.info('Set DDC bus on port #%d to enabled %r', port_id, enabled) |
| 328 | self._flows[port_id].SetDdcState(enabled) |
| 329 | |
| 330 | @_VideoMethod |
| 331 | def IsDdcEnabled(self, port_id): |
| 332 | """Checks if the DDC bus is enabled or disabled on the given video input. |
| 333 | |
| 334 | Args: |
| 335 | port_id: The ID of the video input port. |
| 336 | |
| 337 | Returns: |
| 338 | True if the DDC bus is enabled; False if disabled. |
| 339 | """ |
| 340 | return self._flows[port_id].IsDdcEnabled() |
| 341 | |
| 342 | @_VideoMethod |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 343 | def ReadEdid(self, port_id): |
| 344 | """Reads the EDID content of the selected video input on Chameleon. |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 345 | |
| 346 | Args: |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 347 | port_id: The ID of the video input port. |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 348 | |
| 349 | Returns: |
Tom Wai-Hong Tam | c78b9eb | 2014-12-18 07:49:51 +0800 | [diff] [blame] | 350 | A byte array of EDID data, wrapped in a xmlrpclib.Binary object, |
| 351 | or None if the EDID is disabled. |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 352 | """ |
Tom Wai-Hong Tam | c78b9eb | 2014-12-18 07:49:51 +0800 | [diff] [blame] | 353 | if self._flows[port_id].IsEdidEnabled(): |
| 354 | return xmlrpclib.Binary(self._flows[port_id].ReadEdid()) |
| 355 | else: |
| 356 | logging.debug('Read EDID on port #%d which is disabled.', port_id) |
| 357 | return None |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 358 | |
Tom Wai-Hong Tam | ba7451d | 2014-10-16 06:44:17 +0800 | [diff] [blame] | 359 | @_VideoMethod |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 360 | def ApplyEdid(self, port_id, edid_id): |
| 361 | """Applies the EDID to the selected video input. |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 362 | |
| 363 | Note that this method doesn't pulse the HPD line. Should call Plug(), |
| 364 | Unplug(), or FireHpdPulse() later. |
| 365 | |
| 366 | Args: |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 367 | port_id: The ID of the video input port. |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 368 | edid_id: The ID of the EDID. |
| 369 | """ |
Tom Wai-Hong Tam | c78b9eb | 2014-12-18 07:49:51 +0800 | [diff] [blame] | 370 | if edid_id == ids.EDID_ID_DISABLE: |
| 371 | logging.info('Disable EDID on port #%d', port_id) |
| 372 | self._flows[port_id].SetEdidState(False) |
| 373 | elif edid_id >= ids.EDID_ID_DEFAULT: |
| 374 | logging.info('Apply EDID #%d to port #%d', edid_id, port_id) |
| 375 | self._flows[port_id].WriteEdid(self._all_edids[edid_id]) |
| 376 | self._flows[port_id].SetEdidState(True) |
| 377 | else: |
| 378 | raise DriverError('Not a valid edid_id.') |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 379 | |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 380 | def IsPlugged(self, port_id): |
| 381 | """Returns true if the port is emulated as plugged. |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 382 | |
| 383 | Args: |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 384 | port_id: The ID of the input/output port. |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 385 | |
| 386 | Returns: |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 387 | True if the port is emualted as plugged; otherwise, False. |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 388 | """ |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 389 | return self._flows[port_id].IsPlugged() |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 390 | |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 391 | def Plug(self, port_id): |
| 392 | """Emualtes plug, like asserting HPD line to high on a video port. |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 393 | |
| 394 | Args: |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 395 | port_id: The ID of the input/output port. |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 396 | """ |
Tom Wai-Hong Tam | 6b0b388 | 2014-12-17 04:09:29 +0800 | [diff] [blame] | 397 | logging.info('Plug port #%d', port_id) |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 398 | return self._flows[port_id].Plug() |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 399 | |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 400 | def Unplug(self, port_id): |
| 401 | """Emulates unplug, like deasserting HPD line to low on a video port. |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 402 | |
| 403 | Args: |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 404 | port_id: The ID of the input/output port. |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 405 | """ |
Tom Wai-Hong Tam | 6b0b388 | 2014-12-17 04:09:29 +0800 | [diff] [blame] | 406 | logging.info('Unplug port #%d', port_id) |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 407 | return self._flows[port_id].Unplug() |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 408 | |
Tom Wai-Hong Tam | ba7451d | 2014-10-16 06:44:17 +0800 | [diff] [blame] | 409 | @_VideoMethod |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 410 | def FireHpdPulse(self, port_id, deassert_interval_usec, |
Yuan | 3342be7 | 2014-07-21 18:12:00 +0800 | [diff] [blame] | 411 | assert_interval_usec=None, repeat_count=1, |
| 412 | end_level=1): |
| 413 | """Fires one or more HPD pulse (low -> high -> low -> ...). |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 414 | |
| 415 | Args: |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 416 | port_id: The ID of the video input port. |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 417 | deassert_interval_usec: The time in microsecond of the deassert pulse. |
| 418 | assert_interval_usec: The time in microsecond of the assert pulse. |
Yuan | 3342be7 | 2014-07-21 18:12:00 +0800 | [diff] [blame] | 419 | If None, then use the same value as |
| 420 | deassert_interval_usec. |
| 421 | repeat_count: The count of HPD pulses to fire. |
| 422 | end_level: HPD ends with 0 for LOW (unplugged) or 1 for HIGH (plugged). |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 423 | """ |
Yuan | 3342be7 | 2014-07-21 18:12:00 +0800 | [diff] [blame] | 424 | if assert_interval_usec is None: |
| 425 | # Fall back to use the same value as deassertion if not given. |
| 426 | assert_interval_usec = deassert_interval_usec |
| 427 | |
Tom Wai-Hong Tam | 6b0b388 | 2014-12-17 04:09:29 +0800 | [diff] [blame] | 428 | logging.info('Fire HPD pulse on port #%d, ending with %s', |
| 429 | port_id, 'high' if end_level else 'low') |
Tom Wai-Hong Tam | 0d12884 | 2015-01-14 07:13:45 +0800 | [diff] [blame] | 430 | return self._flows[port_id].FireHpdPulse( |
| 431 | deassert_interval_usec, assert_interval_usec, repeat_count, end_level) |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 432 | |
Tom Wai-Hong Tam | ba7451d | 2014-10-16 06:44:17 +0800 | [diff] [blame] | 433 | @_VideoMethod |
Hung-ying Tyan | 8ebc566 | 2014-11-20 18:37:00 +0800 | [diff] [blame] | 434 | def FireMixedHpdPulses(self, port_id, widths_msec): |
Hung-ying Tyan | 936c83a | 2014-09-11 17:28:44 +0800 | [diff] [blame] | 435 | """Fires one or more HPD pulses, starting at low, of mixed widths. |
| 436 | |
Hung-ying Tyan | 8ebc566 | 2014-11-20 18:37:00 +0800 | [diff] [blame] | 437 | One must specify a list of segment widths in the widths_msec argument where |
| 438 | widths_msec[0] is the width of the first low segment, widths_msec[1] is that |
| 439 | of the first high segment, widths_msec[2] is that of the second low segment, |
| 440 | etc. |
Hung-ying Tyan | 936c83a | 2014-09-11 17:28:44 +0800 | [diff] [blame] | 441 | The HPD line stops at low if even number of segment widths are specified; |
| 442 | otherwise, it stops at high. |
| 443 | |
Hung-ying Tyan | 8ebc566 | 2014-11-20 18:37:00 +0800 | [diff] [blame] | 444 | The method is equivalent to a series of calls to Unplug() and Plug() |
| 445 | separated by specified pulse widths. |
| 446 | |
Hung-ying Tyan | 936c83a | 2014-09-11 17:28:44 +0800 | [diff] [blame] | 447 | Args: |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 448 | port_id: The ID of the video input port. |
Hung-ying Tyan | 8ebc566 | 2014-11-20 18:37:00 +0800 | [diff] [blame] | 449 | widths_msec: list of pulse segment widths in milli-second. |
Hung-ying Tyan | 936c83a | 2014-09-11 17:28:44 +0800 | [diff] [blame] | 450 | """ |
Tom Wai-Hong Tam | 6b0b388 | 2014-12-17 04:09:29 +0800 | [diff] [blame] | 451 | logging.info('Fire mixed HPD pulse on port #%d, ending with %s', |
| 452 | port_id, 'high' if len(widths_msec) % 2 else 'low') |
Hung-ying Tyan | 8ebc566 | 2014-11-20 18:37:00 +0800 | [diff] [blame] | 453 | return self._flows[port_id].FireMixedHpdPulses(widths_msec) |
Hung-ying Tyan | 936c83a | 2014-09-11 17:28:44 +0800 | [diff] [blame] | 454 | |
Tom Wai-Hong Tam | b51a196 | 2014-12-24 02:19:12 +0800 | [diff] [blame] | 455 | @_VideoMethod |
Tom Wai-Hong Tam | 50bc4c9 | 2015-01-09 02:54:36 +0800 | [diff] [blame] | 456 | def SetContentProtection(self, port_id, enabled): |
Tom Wai-Hong Tam | b51a196 | 2014-12-24 02:19:12 +0800 | [diff] [blame] | 457 | """Sets the content protection state on the port. |
| 458 | |
| 459 | Args: |
| 460 | port_id: The ID of the video input port. |
Tom Wai-Hong Tam | 50bc4c9 | 2015-01-09 02:54:36 +0800 | [diff] [blame] | 461 | enabled: True to enable; False to disable. |
Tom Wai-Hong Tam | b51a196 | 2014-12-24 02:19:12 +0800 | [diff] [blame] | 462 | """ |
Tom Wai-Hong Tam | 50bc4c9 | 2015-01-09 02:54:36 +0800 | [diff] [blame] | 463 | logging.info('Set content protection on port #%d: %r', port_id, enabled) |
| 464 | self._flows[port_id].SetContentProtection(enabled) |
Tom Wai-Hong Tam | b51a196 | 2014-12-24 02:19:12 +0800 | [diff] [blame] | 465 | |
| 466 | @_VideoMethod |
| 467 | def IsContentProtectionEnabled(self, port_id): |
| 468 | """Returns True if the content protection is enabled on the port. |
| 469 | |
| 470 | Args: |
| 471 | port_id: The ID of the video input port. |
| 472 | |
| 473 | Returns: |
| 474 | True if the content protection is enabled; otherwise, False. |
| 475 | """ |
| 476 | return self._flows[port_id].IsContentProtectionEnabled() |
| 477 | |
| 478 | @_VideoMethod |
| 479 | def IsVideoInputEncrypted(self, port_id): |
| 480 | """Returns True if the video input on the port is encrypted. |
| 481 | |
| 482 | Args: |
| 483 | port_id: The ID of the video input port. |
| 484 | |
| 485 | Returns: |
| 486 | True if the video input is encrypted; otherwise, False. |
| 487 | """ |
| 488 | return self._flows[port_id].IsVideoInputEncrypted() |
| 489 | |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 490 | def _SelectInput(self, port_id): |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 491 | """Selects the input on Chameleon. |
| 492 | |
| 493 | Args: |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 494 | port_id: The ID of the input port. |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 495 | """ |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 496 | if port_id != self._selected_input: |
| 497 | self._flows[port_id].Select() |
| 498 | self._selected_input = port_id |
| 499 | self._flows[port_id].Do_FSM() |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 500 | |
Cheng-Yi Chiang | 9e62a86 | 2014-10-09 19:16:01 +0800 | [diff] [blame] | 501 | def _SelectOutput(self, port_id): |
| 502 | """Selects the output on Chameleon. |
| 503 | |
| 504 | Args: |
| 505 | port_id: The ID of the output port. |
| 506 | """ |
| 507 | if port_id != self._selected_output: |
| 508 | self._flows[port_id].Select() |
| 509 | self._selected_output = port_id |
| 510 | self._flows[port_id].Do_FSM() |
| 511 | |
Tom Wai-Hong Tam | ba7451d | 2014-10-16 06:44:17 +0800 | [diff] [blame] | 512 | @_VideoMethod |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 513 | def DumpPixels(self, port_id, x=None, y=None, width=None, height=None): |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 514 | """Dumps the raw pixel array of the selected area. |
| 515 | |
| 516 | If not given the area, default to capture the whole screen. |
| 517 | |
| 518 | Args: |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 519 | port_id: The ID of the video input port. |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 520 | x: The X position of the top-left corner. |
| 521 | y: The Y position of the top-left corner. |
| 522 | width: The width of the area. |
| 523 | height: The height of the area. |
| 524 | |
| 525 | Returns: |
| 526 | A byte-array of the pixels, wrapped in a xmlrpclib.Binary object. |
| 527 | """ |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 528 | x, y, width, height = self._AutoFillArea(port_id, x, y, width, height) |
| 529 | self.CaptureVideo(port_id, self._DEFAULT_FRAME_LIMIT, x, y, width, height) |
Tom Wai-Hong Tam | 97bbdd3 | 2014-07-24 05:35:45 +0800 | [diff] [blame] | 530 | return self.ReadCapturedFrame(self._DEFAULT_FRAME_INDEX) |
Tom Wai-Hong Tam | 9ab344a | 2014-06-17 03:17:36 +0800 | [diff] [blame] | 531 | |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 532 | def _AutoFillArea(self, port_id, x, y, width, height): |
Tom Wai-Hong Tam | 12609b2 | 2014-08-01 01:36:24 +0800 | [diff] [blame] | 533 | """Verifies the area argument correctness and fills the default values. |
| 534 | |
Tom Wai-Hong Tam | 0d490c1 | 2014-08-21 09:22:14 +0800 | [diff] [blame] | 535 | It keeps x=None and y=None if all of the x, y, width, and height are None. |
| 536 | That hints FPGA to use a full-screen capture, not a cropped-sccren capture. |
| 537 | |
Tom Wai-Hong Tam | 12609b2 | 2014-08-01 01:36:24 +0800 | [diff] [blame] | 538 | Args: |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 539 | port_id: The ID of the video input port. |
Tom Wai-Hong Tam | 12609b2 | 2014-08-01 01:36:24 +0800 | [diff] [blame] | 540 | x: The X position of the top-left corner. |
| 541 | y: The Y position of the top-left corner. |
| 542 | width: The width of the area. |
| 543 | height: The height of the area. |
| 544 | |
| 545 | Returns: |
| 546 | A tuple of (x, y, width, height) |
| 547 | |
| 548 | Raises: |
| 549 | DriverError if the area is not specified correctly. |
| 550 | """ |
| 551 | if (x, y, width, height) == (None, ) * 4: |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 552 | return (None, None) + self.DetectResolution(port_id) |
Tom Wai-Hong Tam | 0d490c1 | 2014-08-21 09:22:14 +0800 | [diff] [blame] | 553 | elif (x, y) == (None, ) * 2 or None not in (x, y, width, height): |
Tom Wai-Hong Tam | 12609b2 | 2014-08-01 01:36:24 +0800 | [diff] [blame] | 554 | return (x, y, width, height) |
| 555 | else: |
| 556 | raise DriverError('Some of area arguments are not specified.') |
| 557 | |
Tom Wai-Hong Tam | ba7451d | 2014-10-16 06:44:17 +0800 | [diff] [blame] | 558 | @_VideoMethod |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 559 | def GetMaxFrameLimit(self, port_id, width, height): |
Tom Wai-Hong Tam | dc3780d | 2014-08-15 00:45:13 +0800 | [diff] [blame] | 560 | """Gets the maximal number of frames which are accommodated in the buffer. |
Tom Wai-Hong Tam | 9ab344a | 2014-06-17 03:17:36 +0800 | [diff] [blame] | 561 | |
| 562 | It depends on the size of the internal buffer on the board and the |
Tom Wai-Hong Tam | dc3780d | 2014-08-15 00:45:13 +0800 | [diff] [blame] | 563 | size of area to capture (full screen or cropped area). |
Tom Wai-Hong Tam | 9ab344a | 2014-06-17 03:17:36 +0800 | [diff] [blame] | 564 | |
| 565 | Args: |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 566 | port_id: The ID of the video input port. |
Tom Wai-Hong Tam | dc3780d | 2014-08-15 00:45:13 +0800 | [diff] [blame] | 567 | width: The width of the area to capture. |
| 568 | height: The height of the area to capture. |
Tom Wai-Hong Tam | 9ab344a | 2014-06-17 03:17:36 +0800 | [diff] [blame] | 569 | |
| 570 | Returns: |
| 571 | A number of the frame limit. |
| 572 | """ |
Tom Wai-Hong Tam | 6bb537f | 2015-03-19 02:43:08 +0800 | [diff] [blame] | 573 | # This result is related to the video flow status, e.g. |
| 574 | # single/dual pixel mode, progressive/interlaced mode. |
| 575 | # Need to select the input flow first. |
| 576 | self._SelectInput(port_id) |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 577 | return self._flows[port_id].GetMaxFrameLimit(width, height) |
Tom Wai-Hong Tam | 9ab344a | 2014-06-17 03:17:36 +0800 | [diff] [blame] | 578 | |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 579 | def _PrepareCapturingVideo(self, port_id, x, y, width, height): |
| 580 | """Prepares capturing video on the given video input. |
Tom Wai-Hong Tam | 12609b2 | 2014-08-01 01:36:24 +0800 | [diff] [blame] | 581 | |
| 582 | Args: |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 583 | port_id: The ID of the video input port. |
Tom Wai-Hong Tam | 12609b2 | 2014-08-01 01:36:24 +0800 | [diff] [blame] | 584 | x: The X position of the top-left corner of crop. |
| 585 | y: The Y position of the top-left corner of crop. |
| 586 | width: The width of the area of crop. |
| 587 | height: The height of the area of crop. |
| 588 | """ |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 589 | self._SelectInput(port_id) |
| 590 | if not self.IsPlugged(port_id): |
Tom Wai-Hong Tam | 12609b2 | 2014-08-01 01:36:24 +0800 | [diff] [blame] | 591 | raise DriverError('HPD is unplugged. No signal is expected.') |
Tom Wai-Hong Tam | 12609b2 | 2014-08-01 01:36:24 +0800 | [diff] [blame] | 592 | self._captured_params = { |
Tom Wai-Hong Tam | 0d12884 | 2015-01-14 07:13:45 +0800 | [diff] [blame] | 593 | 'port_id': port_id, |
Tom Wai-Hong Tam | 6bb537f | 2015-03-19 02:43:08 +0800 | [diff] [blame] | 594 | 'max_frame_limit': self._flows[port_id].GetMaxFrameLimit(width, height) |
Tom Wai-Hong Tam | 12609b2 | 2014-08-01 01:36:24 +0800 | [diff] [blame] | 595 | } |
| 596 | |
Tom Wai-Hong Tam | ba7451d | 2014-10-16 06:44:17 +0800 | [diff] [blame] | 597 | @_VideoMethod |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 598 | def StartCapturingVideo(self, port_id, x=None, y=None, width=None, |
Tom Wai-Hong Tam | 12609b2 | 2014-08-01 01:36:24 +0800 | [diff] [blame] | 599 | height=None): |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 600 | """Starts video capturing continuously on the given video input. |
Tom Wai-Hong Tam | 12609b2 | 2014-08-01 01:36:24 +0800 | [diff] [blame] | 601 | |
| 602 | This API is an asynchronous call. It returns after the video starts |
| 603 | capturing. The caller should call StopCapturingVideo to stop it. |
| 604 | |
| 605 | The example of usage: |
| 606 | chameleon.StartCapturingVideo(hdmi_input) |
| 607 | time.sleep(2) |
| 608 | chameleon.StopCapturingVideo() |
| 609 | for i in xrange(chameleon.GetCapturedFrameCount()): |
| 610 | frame = chameleon.ReadCapturedFrame(i, *area).data |
| 611 | CompareFrame(frame, golden_frames[i]) |
| 612 | |
| 613 | Args: |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 614 | port_id: The ID of the video input port. |
Tom Wai-Hong Tam | 12609b2 | 2014-08-01 01:36:24 +0800 | [diff] [blame] | 615 | x: The X position of the top-left corner of crop. |
| 616 | y: The Y position of the top-left corner of crop. |
| 617 | width: The width of the area of crop. |
| 618 | height: The height of the area of crop. |
| 619 | """ |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 620 | x, y, width, height = self._AutoFillArea(port_id, x, y, width, height) |
| 621 | self._PrepareCapturingVideo(port_id, x, y, width, height) |
Tom Wai-Hong Tam | 6bb537f | 2015-03-19 02:43:08 +0800 | [diff] [blame] | 622 | max_frame_limit = self._captured_params['max_frame_limit'] |
Tom Wai-Hong Tam | 6b0b388 | 2014-12-17 04:09:29 +0800 | [diff] [blame] | 623 | logging.info('Start capturing video from port #%d', port_id) |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 624 | self._flows[port_id].StartDumpingFrames( |
Tom Wai-Hong Tam | 12609b2 | 2014-08-01 01:36:24 +0800 | [diff] [blame] | 625 | max_frame_limit, x, y, width, height, self._MAX_CAPTURED_FRAME_COUNT) |
| 626 | |
Tom Wai-Hong Tam | f12bfa1 | 2014-12-18 09:20:39 +0800 | [diff] [blame] | 627 | def StopCapturingVideo(self, stop_index=None): |
Tom Wai-Hong Tam | 12609b2 | 2014-08-01 01:36:24 +0800 | [diff] [blame] | 628 | """Stops video capturing which was started previously. |
| 629 | |
Tom Wai-Hong Tam | f12bfa1 | 2014-12-18 09:20:39 +0800 | [diff] [blame] | 630 | Args: |
| 631 | stop_index: Wait for the captured frame count to reach this index. If |
| 632 | not given, stop immediately. Note that the captured frame of |
| 633 | stop_index should not be read. |
| 634 | |
Tom Wai-Hong Tam | 12609b2 | 2014-08-01 01:36:24 +0800 | [diff] [blame] | 635 | Raises: |
| 636 | DriverError if the capture period is longer than the capture limitation. |
| 637 | """ |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 638 | port_id = self._captured_params['port_id'] |
Tom Wai-Hong Tam | f12bfa1 | 2014-12-18 09:20:39 +0800 | [diff] [blame] | 639 | if stop_index: |
| 640 | if stop_index >= self._MAX_CAPTURED_FRAME_COUNT: |
| 641 | raise DriverError('Exceeded the limit of capture, stop_index >= %d' % |
| 642 | self._MAX_CAPTURED_FRAME_COUNT) |
| 643 | logging.info('Waiting the captured frame count reaches %d...', stop_index) |
| 644 | while self.GetCapturedFrameCount() < stop_index: |
| 645 | pass |
| 646 | |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 647 | self._flows[port_id].StopDumpingFrames() |
Tom Wai-Hong Tam | 6b0b388 | 2014-12-17 04:09:29 +0800 | [diff] [blame] | 648 | logging.info('Stopped capturing video from port #%d', port_id) |
Tom Wai-Hong Tam | 12609b2 | 2014-08-01 01:36:24 +0800 | [diff] [blame] | 649 | if self.GetCapturedFrameCount() >= self._MAX_CAPTURED_FRAME_COUNT: |
| 650 | raise DriverError('Exceeded the limit of capture, frame_count >= %d' % |
| 651 | self._MAX_CAPTURED_FRAME_COUNT) |
| 652 | |
Tom Wai-Hong Tam | ba7451d | 2014-10-16 06:44:17 +0800 | [diff] [blame] | 653 | @_VideoMethod |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 654 | def CaptureVideo(self, port_id, total_frame, x=None, y=None, width=None, |
Tom Wai-Hong Tam | 97bbdd3 | 2014-07-24 05:35:45 +0800 | [diff] [blame] | 655 | height=None): |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 656 | """Captures the video stream on the given video input to the buffer. |
Tom Wai-Hong Tam | 9ab344a | 2014-06-17 03:17:36 +0800 | [diff] [blame] | 657 | |
| 658 | This API is a synchronous call. It returns after all the frames are |
| 659 | captured. The frames can be read using the ReadCapturedFrame API. |
| 660 | |
| 661 | The example of usage: |
| 662 | chameleon.CaptureVideo(hdmi_input, total_frame) |
| 663 | for i in xrange(total_frame): |
| 664 | frame = chameleon.ReadCapturedFrame(i, *area).data |
| 665 | CompareFrame(frame, golden_frames[i]) |
| 666 | |
| 667 | Args: |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 668 | port_id: The ID of the video input port. |
Tom Wai-Hong Tam | 9ab344a | 2014-06-17 03:17:36 +0800 | [diff] [blame] | 669 | total_frame: The total number of frames to capture, should not larger |
| 670 | than value of GetMaxFrameLimit. |
Tom Wai-Hong Tam | 97bbdd3 | 2014-07-24 05:35:45 +0800 | [diff] [blame] | 671 | x: The X position of the top-left corner of crop. |
| 672 | y: The Y position of the top-left corner of crop. |
| 673 | width: The width of the area of crop. |
| 674 | height: The height of the area of crop. |
Tom Wai-Hong Tam | 9ab344a | 2014-06-17 03:17:36 +0800 | [diff] [blame] | 675 | """ |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 676 | x, y, width, height = self._AutoFillArea(port_id, x, y, width, height) |
Tom Wai-Hong Tam | 6b0b388 | 2014-12-17 04:09:29 +0800 | [diff] [blame] | 677 | logging.info('Capture video from port #%d', port_id) |
Tom Wai-Hong Tam | 6bb537f | 2015-03-19 02:43:08 +0800 | [diff] [blame] | 678 | self._PrepareCapturingVideo(port_id, x, y, width, height) |
| 679 | max_frame_limit = self._captured_params['max_frame_limit'] |
Tom Wai-Hong Tam | 9ab344a | 2014-06-17 03:17:36 +0800 | [diff] [blame] | 680 | if total_frame > max_frame_limit: |
| 681 | raise DriverError('Exceed the max frame limit %d > %d', |
| 682 | total_frame, max_frame_limit) |
| 683 | |
Tom Wai-Hong Tam | 268def6 | 2014-07-09 07:45:33 +0800 | [diff] [blame] | 684 | # TODO(waihong): Make the timeout value based on the FPS rate. |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 685 | self._flows[port_id].DumpFramesToLimit( |
Tom Wai-Hong Tam | d1266e8 | 2014-07-26 07:46:02 +0800 | [diff] [blame] | 686 | total_frame, x, y, width, height, self._TIMEOUT_FRAME_DUMP_PROBE) |
Tom Wai-Hong Tam | 97bbdd3 | 2014-07-24 05:35:45 +0800 | [diff] [blame] | 687 | |
Tom Wai-Hong Tam | 12609b2 | 2014-08-01 01:36:24 +0800 | [diff] [blame] | 688 | def GetCapturedFrameCount(self): |
| 689 | """Gets the total count of the captured frames. |
Tom Wai-Hong Tam | 97bbdd3 | 2014-07-24 05:35:45 +0800 | [diff] [blame] | 690 | |
Tom Wai-Hong Tam | 12609b2 | 2014-08-01 01:36:24 +0800 | [diff] [blame] | 691 | Returns: |
| 692 | The number of frames captured. |
| 693 | """ |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 694 | port_id = self._captured_params['port_id'] |
| 695 | return self._flows[port_id].GetDumpedFrameCount() |
Tom Wai-Hong Tam | f2b1a20 | 2014-06-17 03:04:34 +0800 | [diff] [blame] | 696 | |
| 697 | def GetCapturedResolution(self): |
| 698 | """Gets the resolution of the captured frame. |
| 699 | |
Tom Wai-Hong Tam | 97bbdd3 | 2014-07-24 05:35:45 +0800 | [diff] [blame] | 700 | If a cropping area is specified on capturing, returns the cropped |
| 701 | resolution. |
| 702 | |
Tom Wai-Hong Tam | f2b1a20 | 2014-06-17 03:04:34 +0800 | [diff] [blame] | 703 | Returns: |
| 704 | A (width, height) tuple. |
| 705 | """ |
Tom Wai-Hong Tam | be82d37 | 2015-03-19 07:31:28 +0800 | [diff] [blame^] | 706 | port_id = self._captured_params['port_id'] |
| 707 | return self._flows[port_id].GetCapturedResolution() |
Tom Wai-Hong Tam | f2b1a20 | 2014-06-17 03:04:34 +0800 | [diff] [blame] | 708 | |
Tom Wai-Hong Tam | 97bbdd3 | 2014-07-24 05:35:45 +0800 | [diff] [blame] | 709 | def ReadCapturedFrame(self, frame_index): |
Tom Wai-Hong Tam | be82d37 | 2015-03-19 07:31:28 +0800 | [diff] [blame^] | 710 | """Reads the content of the captured frame from the buffer. |
Tom Wai-Hong Tam | f2b1a20 | 2014-06-17 03:04:34 +0800 | [diff] [blame] | 711 | |
| 712 | Args: |
| 713 | frame_index: The index of the frame to read. |
Tom Wai-Hong Tam | f2b1a20 | 2014-06-17 03:04:34 +0800 | [diff] [blame] | 714 | |
| 715 | Returns: |
| 716 | A byte-array of the pixels, wrapped in a xmlrpclib.Binary object. |
| 717 | """ |
Tom Wai-Hong Tam | dd6ce4e | 2014-12-16 06:34:38 +0800 | [diff] [blame] | 718 | port_id = self._captured_params['port_id'] |
Tom Wai-Hong Tam | 12609b2 | 2014-08-01 01:36:24 +0800 | [diff] [blame] | 719 | total_frame = self.GetCapturedFrameCount() |
Tom Wai-Hong Tam | 6bb537f | 2015-03-19 02:43:08 +0800 | [diff] [blame] | 720 | max_frame_limit = self._captured_params['max_frame_limit'] |
Tom Wai-Hong Tam | dd6ce4e | 2014-12-16 06:34:38 +0800 | [diff] [blame] | 721 | # The captured frames are store in a circular buffer. Only the latest |
| 722 | # max_frame_limit frames are valid. |
| 723 | first_valid_index = max(0, total_frame - max_frame_limit) |
| 724 | if not first_valid_index <= frame_index < total_frame: |
| 725 | raise DriverError('The frame index is out-of-range: %d not in [%d, %d)' % |
| 726 | (frame_index, first_valid_index, total_frame)) |
| 727 | |
Tom Wai-Hong Tam | be82d37 | 2015-03-19 07:31:28 +0800 | [diff] [blame^] | 728 | # Use the projected index. |
| 729 | frame_index = frame_index % max_frame_limit |
| 730 | screen = self._flows[port_id].ReadCapturedFrame(frame_index) |
Tom Wai-Hong Tam | 59d7702 | 2014-05-31 00:47:46 +0800 | [diff] [blame] | 731 | return xmlrpclib.Binary(screen) |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 732 | |
Tom Wai-Hong Tam | 3610cce | 2014-12-13 08:57:27 +0800 | [diff] [blame] | 733 | def GetCapturedChecksums(self, start_index=0, stop_index=None): |
Tom Wai-Hong Tam | d1266e8 | 2014-07-26 07:46:02 +0800 | [diff] [blame] | 734 | """Gets the list of checksums of the captured frames. |
Tom Wai-Hong Tam | 4f769e0 | 2014-07-09 07:54:40 +0800 | [diff] [blame] | 735 | |
| 736 | Args: |
Tom Wai-Hong Tam | 3610cce | 2014-12-13 08:57:27 +0800 | [diff] [blame] | 737 | start_index: The index of the start frame. Default is 0. |
| 738 | stop_index: The index of the stop frame (excluded). Default is the |
| 739 | value of GetCapturedFrameCount. |
Tom Wai-Hong Tam | 4f769e0 | 2014-07-09 07:54:40 +0800 | [diff] [blame] | 740 | |
| 741 | Returns: |
Tom Wai-Hong Tam | d1266e8 | 2014-07-26 07:46:02 +0800 | [diff] [blame] | 742 | The list of checksums of frames. |
Tom Wai-Hong Tam | 4f769e0 | 2014-07-09 07:54:40 +0800 | [diff] [blame] | 743 | """ |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 744 | port_id = self._captured_params['port_id'] |
Tom Wai-Hong Tam | 12609b2 | 2014-08-01 01:36:24 +0800 | [diff] [blame] | 745 | total_frame = self.GetCapturedFrameCount() |
Tom Wai-Hong Tam | 3610cce | 2014-12-13 08:57:27 +0800 | [diff] [blame] | 746 | if stop_index is None: |
| 747 | stop_index = total_frame |
Tom Wai-Hong Tam | 12609b2 | 2014-08-01 01:36:24 +0800 | [diff] [blame] | 748 | if not 0 <= start_index < total_frame: |
| 749 | raise DriverError('The start index is out-of-range: %d not in [0, %d)' % |
| 750 | (start_index, total_frame)) |
| 751 | if not 0 < stop_index <= total_frame: |
| 752 | raise DriverError('The stop index is out-of-range: %d not in (0, %d]' % |
| 753 | (stop_index, total_frame)) |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 754 | return self._flows[port_id].GetFrameHashes(start_index, stop_index) |
Tom Wai-Hong Tam | 4f769e0 | 2014-07-09 07:54:40 +0800 | [diff] [blame] | 755 | |
Tom Wai-Hong Tam | ba7451d | 2014-10-16 06:44:17 +0800 | [diff] [blame] | 756 | @_VideoMethod |
Tom Wai-Hong Tam | 0d12884 | 2015-01-14 07:13:45 +0800 | [diff] [blame] | 757 | def ComputePixelChecksum( |
| 758 | self, port_id, x=None, y=None, width=None, height=None): |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 759 | """Computes the checksum of pixels in the selected area. |
| 760 | |
| 761 | If not given the area, default to compute the whole screen. |
| 762 | |
| 763 | Args: |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 764 | port_id: The ID of the video input port. |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 765 | x: The X position of the top-left corner. |
| 766 | y: The Y position of the top-left corner. |
| 767 | width: The width of the area. |
| 768 | height: The height of the area. |
| 769 | |
| 770 | Returns: |
| 771 | The checksum of the pixels. |
| 772 | """ |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 773 | x, y, width, height = self._AutoFillArea(port_id, x, y, width, height) |
| 774 | self.CaptureVideo(port_id, self._DEFAULT_FRAME_LIMIT, x, y, width, height) |
Tom Wai-Hong Tam | d1266e8 | 2014-07-26 07:46:02 +0800 | [diff] [blame] | 775 | return self.GetCapturedChecksums(self._DEFAULT_FRAME_INDEX, |
| 776 | self._DEFAULT_FRAME_INDEX + 1)[0] |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 777 | |
Tom Wai-Hong Tam | ba7451d | 2014-10-16 06:44:17 +0800 | [diff] [blame] | 778 | @_VideoMethod |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 779 | def DetectResolution(self, port_id): |
| 780 | """Detects the video source resolution. |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 781 | |
| 782 | Args: |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 783 | port_id: The ID of the video input port. |
Tom Wai-Hong Tam | 0703c54 | 2014-05-16 14:13:26 +0800 | [diff] [blame] | 784 | |
| 785 | Returns: |
| 786 | A (width, height) tuple. |
| 787 | """ |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 788 | self._SelectInput(port_id) |
Tom Wai-Hong Tam | 6b0b388 | 2014-12-17 04:09:29 +0800 | [diff] [blame] | 789 | resolution = self._flows[port_id].GetResolution() |
| 790 | logging.info('Detected resolution on port #%d: %dx%d', port_id, *resolution) |
| 791 | return resolution |
Cheng-Yi Chiang | 6048d00 | 2014-07-30 17:51:31 +0800 | [diff] [blame] | 792 | |
Cheng-Yi Chiang | 32cbd45 | 2015-02-22 17:54:35 +0800 | [diff] [blame] | 793 | def HasAudioBoard(self): |
| 794 | """Returns True if there is an audio board. |
| 795 | |
| 796 | Returns: |
| 797 | True if there is an audio board. False otherwise. |
| 798 | """ |
| 799 | return self._audio_board is not None |
| 800 | |
Cheng-Yi Chiang | 9e62a86 | 2014-10-09 19:16:01 +0800 | [diff] [blame] | 801 | @_AudioMethod(input_only=True) |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 802 | def StartCapturingAudio(self, port_id): |
Cheng-Yi Chiang | 6048d00 | 2014-07-30 17:51:31 +0800 | [diff] [blame] | 803 | """Starts capturing audio. |
| 804 | |
Cheng-Yi Chiang | b8b247e | 2015-01-20 11:41:51 +0800 | [diff] [blame] | 805 | Refer to the docstring of StartPlayingEcho about the restriction of |
| 806 | capturing and echoing at the same time. |
| 807 | |
Cheng-Yi Chiang | 6048d00 | 2014-07-30 17:51:31 +0800 | [diff] [blame] | 808 | Args: |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 809 | port_id: The ID of the audio input port. |
Cheng-Yi Chiang | 6048d00 | 2014-07-30 17:51:31 +0800 | [diff] [blame] | 810 | """ |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 811 | self._SelectInput(port_id) |
Cheng-Yi Chiang | 9e62a86 | 2014-10-09 19:16:01 +0800 | [diff] [blame] | 812 | logging.info('Start capturing audio from port #%d', port_id) |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 813 | self._flows[port_id].StartCapturingAudio() |
Cheng-Yi Chiang | 6048d00 | 2014-07-30 17:51:31 +0800 | [diff] [blame] | 814 | |
Cheng-Yi Chiang | 9e62a86 | 2014-10-09 19:16:01 +0800 | [diff] [blame] | 815 | @_AudioMethod(input_only=True) |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 816 | def StopCapturingAudio(self, port_id): |
Cheng-Yi Chiang | 6048d00 | 2014-07-30 17:51:31 +0800 | [diff] [blame] | 817 | """Stops capturing audio and returns recorded audio raw data. |
| 818 | |
| 819 | Args: |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 820 | port_id: The ID of the audio input port. |
Cheng-Yi Chiang | 6048d00 | 2014-07-30 17:51:31 +0800 | [diff] [blame] | 821 | |
| 822 | Returns: |
| 823 | A tuple (data, format). |
| 824 | data: The captured audio data wrapped in an xmlrpclib.Binary object. |
| 825 | format: The dict representation of AudioDataFormat. Refer to docstring |
| 826 | of utils.audio.AudioDataFormat for detail. |
Cheng-Yi Chiang | dd9511c | 2014-10-01 14:45:58 +0800 | [diff] [blame] | 827 | Currently, the data format supported is |
| 828 | dict(file_type='raw', sample_format='S32_LE', channel=8, rate=48000) |
Cheng-Yi Chiang | 9e62a86 | 2014-10-09 19:16:01 +0800 | [diff] [blame] | 829 | |
| 830 | Raises: |
| 831 | DriverError: Input is selected to port other than port_id. |
| 832 | This happens if user has used API related to input operation on |
| 833 | other port. The API includes CaptureVideo, StartCapturingVideo, |
| 834 | DetectResolution, StartCapturingAudio, StartPlayingEcho. |
Cheng-Yi Chiang | 6048d00 | 2014-07-30 17:51:31 +0800 | [diff] [blame] | 835 | """ |
Tom Wai-Hong Tam | 0d12884 | 2015-01-14 07:13:45 +0800 | [diff] [blame] | 836 | if self._selected_input != port_id: |
Cheng-Yi Chiang | dd9511c | 2014-10-01 14:45:58 +0800 | [diff] [blame] | 837 | raise DriverError( |
Tom Wai-Hong Tam | 47ebf23 | 2014-10-15 08:06:09 +0800 | [diff] [blame] | 838 | 'The input is selected to %r not %r', self._selected_input, port_id) |
| 839 | data, data_format = self._flows[port_id].StopCapturingAudio() |
Cheng-Yi Chiang | 9e62a86 | 2014-10-09 19:16:01 +0800 | [diff] [blame] | 840 | logging.info('Stopped capturing audio from port #%d', port_id) |
Cheng-Yi Chiang | 6048d00 | 2014-07-30 17:51:31 +0800 | [diff] [blame] | 841 | return xmlrpclib.Binary(data), data_format |
Cheng-Yi Chiang | 9e62a86 | 2014-10-09 19:16:01 +0800 | [diff] [blame] | 842 | |
| 843 | @_AudioMethod(output_only=True) |
| 844 | def StartPlayingAudio(self, port_id, data, data_format): |
| 845 | """Playing audio data from an output port. |
| 846 | |
| 847 | Unwrap audio data and play that data from port_id port. |
Cheng-Yi Chiang | 9e62a86 | 2014-10-09 19:16:01 +0800 | [diff] [blame] | 848 | |
| 849 | Args: |
| 850 | port_id: The ID of the output connector. |
| 851 | data: The audio data to play wrapped in xmlrpclib.Binary. |
| 852 | data_format: The dict representation of AudioDataFormat. |
| 853 | Refer to docstring of utils.audio.AudioDataFormat for detail. |
| 854 | Currently Chameleon only accepts data format if it meets |
| 855 | dict(file_type='raw', sample_format='S32_LE', channel=8, rate=48000) |
| 856 | Chameleon user should do the format conversion to minimize work load |
| 857 | on Chameleon board. |
| 858 | |
| 859 | Raises: |
| 860 | DriverError: There is any audio input port recording. |
| 861 | """ |
Cheng-Yi Chiang | 9e62a86 | 2014-10-09 19:16:01 +0800 | [diff] [blame] | 862 | self._SelectOutput(port_id) |
| 863 | logging.info('Start playing audio from port #%d', port_id) |
| 864 | self._flows[port_id].StartPlayingAudioData((data.data, data_format)) |
| 865 | |
| 866 | @_AudioMethod(output_only=True) |
| 867 | def StartPlayingEcho(self, port_id, input_id): |
| 868 | """Echoes audio data received from input_id and plays to port_id. |
| 869 | |
| 870 | Echoes audio data received from input_id and plays to port_id. |
| 871 | |
Cheng-Yi Chiang | b8b247e | 2015-01-20 11:41:51 +0800 | [diff] [blame] | 872 | Chameleon does not support echoing from HDMI and capturing from LineIn/Mic |
| 873 | at the same time. The echoing/capturing needs to be stop first before |
| 874 | another action starts. |
| 875 | |
| 876 | For example, user can call |
| 877 | |
| 878 | StartPlayingEcho(3, 7) --> StopPlayingAudio(3) --> StartCapturingAudio(6) |
| 879 | |
| 880 | or |
| 881 | |
| 882 | StartCapturingAudio(6) --> StopCapturingAudio(6) --> StartPlayingEcho(3, 7) |
| 883 | |
| 884 | but user can not call |
| 885 | |
| 886 | StartPlayingEcho(3, 7) --> StartCapturingAudio(6) |
| 887 | |
| 888 | or |
| 889 | |
| 890 | StartCapturingAudio(6) --> StartPlayingEcho(3, 7) |
| 891 | |
| 892 | Exception is raised when conflicting actions are performed. |
| 893 | |
Cheng-Yi Chiang | 9e62a86 | 2014-10-09 19:16:01 +0800 | [diff] [blame] | 894 | Args: |
| 895 | port_id: The ID of the output connector. Check the value in ids.py. |
| 896 | input_id: The ID of the input connector. Check the value in ids.py. |
| 897 | |
| 898 | Raises: |
| 899 | DriverError: input_id is not valid for audio operation. |
| 900 | """ |
| 901 | if not ids.IsAudioPort(input_id) or not ids.IsInputPort(input_id): |
| 902 | raise DriverError( |
| 903 | 'Not a valid input_id for audio operation: %d' % input_id) |
| 904 | self._SelectInput(input_id) |
| 905 | self._SelectOutput(port_id) |
| 906 | logging.info('Start playing echo from port #%d using source from port#%d', |
| 907 | port_id, input_id) |
| 908 | self._flows[port_id].StartPlayingEcho(input_id) |
| 909 | |
| 910 | @_AudioMethod(output_only=True) |
| 911 | def StopPlayingAudio(self, port_id): |
| 912 | """Stops playing audio from port_id port. |
| 913 | |
| 914 | Args: |
| 915 | port_id: The ID of the output connector. |
| 916 | |
| 917 | Raises: |
| 918 | DriverError: Output is selected to port other than port_id. |
| 919 | This happens if user has used API related to output operation on other |
| 920 | port. The API includes StartPlayingAudio, StartPlayingEcho. |
| 921 | """ |
Tom Wai-Hong Tam | 0d12884 | 2015-01-14 07:13:45 +0800 | [diff] [blame] | 922 | if self._selected_output != port_id: |
Cheng-Yi Chiang | 9e62a86 | 2014-10-09 19:16:01 +0800 | [diff] [blame] | 923 | raise DriverError( |
| 924 | 'The output is selected to %r not %r', self._selected_output, port_id) |
| 925 | logging.info('Stop playing audio from port #%d', port_id) |
| 926 | self._flows[port_id].StopPlayingAudio() |
Cheng-Yi Chiang | 32cbd45 | 2015-02-22 17:54:35 +0800 | [diff] [blame] | 927 | |
| 928 | @_AudioBoardMethod |
| 929 | def AudioBoardConnect(self, bus_number, endpoint): |
| 930 | """Connects an endpoint to an audio bus. |
| 931 | |
| 932 | Args: |
| 933 | bus_number: 1 or 2 for audio bus 1 or bus 2. |
| 934 | endpoint: An endpoint defined in audio_board.AudioBusEndpoint. |
| 935 | |
| 936 | Raises: |
| 937 | DriverError: If the endpoint is a source and there is other source |
| 938 | endpoint occupying audio bus. |
| 939 | """ |
| 940 | if audio_board.IsSource(endpoint): |
| 941 | current_sources, _ = self._audio_board.GetConnections(bus_number) |
| 942 | if current_sources and endpoint not in current_sources: |
| 943 | raise DriverError( |
| 944 | 'Sources %s other than %s are currently occupying audio bus.' % |
| 945 | (current_sources, endpoint)) |
| 946 | |
| 947 | self._audio_board.SetConnection( |
| 948 | bus_number, endpoint, True) |
| 949 | |
| 950 | @_AudioBoardMethod |
| 951 | def AudioBoardDisconnect(self, bus_number, endpoint): |
| 952 | """Disconnects an endpoint to an audio bus. |
| 953 | |
| 954 | Args: |
| 955 | bus_number: 1 or 2 for audio bus 1 or bus 2. |
| 956 | endpoint: An endpoint defined in audio_board.AudioBusEndpoint. |
| 957 | |
| 958 | Raises: |
| 959 | DriverError: If the endpoint is not connected to audio bus. |
| 960 | """ |
| 961 | if not self._audio_board.IsConnected(bus_number, endpoint): |
| 962 | raise DriverError( |
| 963 | 'Endpoint %s is not connected to audio bus %d.' % |
| 964 | (endpoint, bus_number)) |
| 965 | |
| 966 | self._audio_board.SetConnection( |
| 967 | bus_number, endpoint, False) |
| 968 | |
| 969 | @_AudioBoardMethod |
| 970 | def AudioBoardGetRoutes(self, bus_number): |
| 971 | """Gets a list of routes on audio bus. |
| 972 | |
| 973 | Args: |
| 974 | bus_number: 1 or 2 for audio bus 1 or bus 2. |
| 975 | |
| 976 | Returns: |
| 977 | A list of tuples (source, sink) that are routed on audio bus |
| 978 | where source and sink are endpoints defined in |
| 979 | audio_board.AudioBusEndpoint. |
| 980 | """ |
| 981 | sources, sinks = self._audio_board.GetConnections(bus_number) |
| 982 | routes = [] |
| 983 | for source in sources: |
| 984 | for sink in sinks: |
| 985 | logging.info('Route on bus %d: %s ---> %s', |
| 986 | bus_number, source, sink) |
| 987 | routes.append((source, sink)) |
| 988 | return routes |
| 989 | |
| 990 | @_AudioBoardMethod |
| 991 | def AudioBoardClearRoutes(self, bus_number): |
| 992 | """Clears routes on an audio bus. |
| 993 | |
| 994 | Args: |
| 995 | bus_number: 1 or 2 for audio bus 1 or bus 2. |
| 996 | """ |
| 997 | self._audio_board.ResetConnections(bus_number) |