blob: b6c6c85d1c54e38e7e205a18d7818c76e0188047 [file] [log] [blame]
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +08001# 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 Tamba7451d2014-10-16 06:44:17 +08006import functools
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +08007import logging
8import os
9import xmlrpclib
10
11import chameleon_common # pylint: disable=W0611
12from chameleond.interface import ChameleondInterface
Yuan3342be72014-07-21 18:12:00 +080013
Cheng-Yi Chiang32cbd452015-02-22 17:54:35 +080014from chameleond.utils import audio_board
Cheng-Yi Chiang2f53ea22014-10-01 14:58:27 +080015from chameleond.utils import codec_flow
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +080016from chameleond.utils import fpga
Tom Wai-Hong Tamf7f6e3c2014-10-15 04:53:52 +080017from chameleond.utils import i2c
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +080018from chameleond.utils import ids
19from chameleond.utils import input_flow
20
21
22class DriverError(Exception):
23 """Exception raised when any error on FPGA driver."""
24 pass
25
26
Cheng-Yi Chiang9e62a862014-10-09 19:16:01 +080027def _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 Tamba7451d2014-10-16 06:44:17 +080049
50
51def _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 Chiang32cbd452015-02-22 17:54:35 +080061def _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 Tam0703c542014-05-16 14:13:26 +080071class ChameleondDriver(ChameleondInterface):
72 """Chameleond Driver for FPGA customized platform."""
73
74 _I2C_BUS_MAIN = 0
Cheng-Yi Chiang433d9842015-02-22 16:52:35 +080075 _I2C_BUS_AUDIO_CODEC = 1
Cheng-Yi Chiang32cbd452015-02-22 17:54:35 +080076 _I2C_BUS_AUDIO_BOARD = 3
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +080077
Tom Wai-Hong Tam32ce3432014-06-12 09:17:43 +080078 # Time to wait for video frame dump to start before a timeout error is raised
Tom Wai-Hong Tam268def62014-07-09 07:45:33 +080079 _TIMEOUT_FRAME_DUMP_PROBE = 60.0
Tom Wai-Hong Tam32ce3432014-06-12 09:17:43 +080080
Tom Wai-Hong Tamf2b1a202014-06-17 03:04:34 +080081 # The frame index which is used for the regular DumpPixels API.
82 _DEFAULT_FRAME_INDEX = 0
Tom Wai-Hong Tam9ab344a2014-06-17 03:17:36 +080083 _DEFAULT_FRAME_LIMIT = _DEFAULT_FRAME_INDEX + 1
Tom Wai-Hong Tamf2b1a202014-06-17 03:04:34 +080084
Tom Wai-Hong Tam12609b22014-08-01 01:36:24 +080085 # Limit the period of async capture to 3min (in 60fps).
86 _MAX_CAPTURED_FRAME_COUNT = 3 * 60 * 60
87
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +080088 def __init__(self, *args, **kwargs):
89 super(ChameleondDriver, self).__init__(*args, **kwargs)
90 self._selected_input = None
Cheng-Yi Chiang9e62a862014-10-09 19:16:01 +080091 self._selected_output = None
Tom Wai-Hong Tamf2b1a202014-06-17 03:04:34 +080092 self._captured_params = {}
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +080093 # 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 Chiang433d9842015-02-22 16:52:35 +080097 audio_codec_bus = i2c.I2cBus(self._I2C_BUS_AUDIO_CODEC)
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +080098 fpga_ctrl = fpga.FpgaController()
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +080099 self._flows = {
Tom Wai-Hong Tam0d128842015-01-14 07:13:45 +0800100 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 Chiang433d9842015-02-22 16:52:35 +0800104 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 Tam0d128842015-01-14 07:13:45 +0800107 ids.LINEOUT: codec_flow.OutputCodecFlow(
Cheng-Yi Chiang433d9842015-02-22 16:52:35 +0800108 ids.LINEOUT, audio_codec_bus, fpga_ctrl)
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800109 }
110
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800111 for flow in self._flows.itervalues():
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800112 if flow:
113 flow.Initialize()
114
Cheng-Yi Chiang32cbd452015-02-22 17:54:35 +0800115 # 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 Tam0703c542014-05-16 14:13:26 +0800125 self.Reset()
126
127 # Set all ports unplugged on initialization.
Tom Wai-Hong Tam0fca92b2015-01-15 02:13:55 +0800128 for port_id in self.GetSupportedPorts():
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800129 self.Unplug(port_id)
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800130
131 def Reset(self):
132 """Resets Chameleon board."""
Tom Wai-Hong Tam6b0b3882014-12-17 04:09:29 +0800133 logging.info('Execute the reset process')
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800134 # TODO(waihong): Add other reset routines.
Tom Wai-Hong Tamae0907d2015-01-14 09:29:50 +0800135 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 Chiangb8b247e2015-01-20 11:41:51 +0800140 for port_id in self.GetSupportedPorts():
141 if self.HasAudioSupport(port_id):
142 self._flows[port_id].ResetRoute()
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800143
Cheng-Yi Chiang32cbd452015-02-22 17:54:35 +0800144 if self.HasAudioBoard():
145 self._audio_board.Reset()
146
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800147 def GetSupportedPorts(self):
148 """Returns all supported ports on the board.
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800149
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800150 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 Tam0703c542014-05-16 14:13:26 +0800152
153 Returns:
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800154 A tuple of port_id, for all supported ports on the board.
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800155 """
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800156 return self._flows.keys()
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800157
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800158 def GetSupportedInputs(self):
159 """Returns all supported input ports on the board.
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800160
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800161 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 Tamba7451d2014-10-16 06:44:17 +0800167 return ids.INPUT_PORTS
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800168
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 Tamba7451d2014-10-16 06:44:17 +0800178 return ids.OUTPUT_PORTS
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800179
180 def IsPhysicalPlugged(self, port_id):
181 """Returns true if the physical cable is plugged between DUT and Chameleon.
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800182
183 Args:
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800184 port_id: The ID of the input/output port.
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800185
186 Returns:
187 True if the physical cable is plugged; otherwise, False.
188 """
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800189 return self._flows[port_id].IsPhysicalPlugged()
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800190
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800191 def ProbePorts(self):
192 """Probes all the connected ports on Chameleon board.
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800193
194 Returns:
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800195 A tuple of port_id, for the ports connected to DUT.
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800196 """
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800197 return tuple(port_id for port_id in self.GetSupportedPorts()
198 if self.IsPhysicalPlugged(port_id))
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800199
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800200 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 Tam0703c542014-05-16 14:13:26 +0800219 """Returns the human readable string for the connector type.
220
221 Args:
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800222 port_id: The ID of the input/output port.
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800223
224 Returns:
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800225 A string, like "HDMI", "DP", "MIC", etc.
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800226 """
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800227 return self._flows[port_id].GetConnectorType()
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800228
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800229 def HasAudioSupport(self, port_id):
230 """Returns true if the port has audio support.
Tom Wai-Hong Tam1b17ade2014-10-14 07:59:43 +0800231
232 Args:
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800233 port_id: The ID of the input/output port.
Tom Wai-Hong Tam1b17ade2014-10-14 07:59:43 +0800234
235 Returns:
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800236 True if the input/output port has audio support; otherwise, False.
Tom Wai-Hong Tam1b17ade2014-10-14 07:59:43 +0800237 """
Tom Wai-Hong Tamba7451d2014-10-16 06:44:17 +0800238 return ids.IsAudioPort(port_id)
Tom Wai-Hong Tam1b17ade2014-10-14 07:59:43 +0800239
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800240 def HasVideoSupport(self, port_id):
241 """Returns true if the port has video support.
Tom Wai-Hong Tam1b17ade2014-10-14 07:59:43 +0800242
243 Args:
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800244 port_id: The ID of the input/output port.
Tom Wai-Hong Tam1b17ade2014-10-14 07:59:43 +0800245
246 Returns:
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800247 True if the input/output port has video support; otherwise, False.
Tom Wai-Hong Tam1b17ade2014-10-14 07:59:43 +0800248 """
Tom Wai-Hong Tamba7451d2014-10-16 06:44:17 +0800249 return ids.IsVideoPort(port_id)
Tom Wai-Hong Tam1b17ade2014-10-14 07:59:43 +0800250
Tom Wai-Hong Tamba7451d2014-10-16 06:44:17 +0800251 @_VideoMethod
Tom Wai-Hong Tam26009b12014-11-06 07:02:40 +0800252 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 Tam45411412014-11-11 04:07:36 +0800257 mode: A string of the mode name, e.g. 'PC_1920x1080x60'. Use 'auto'
258 to detect the VGA mode automatically.
Tom Wai-Hong Tam26009b12014-11-06 07:02:40 +0800259 """
260 if port_id == ids.VGA:
Tom Wai-Hong Tam6b0b3882014-12-17 04:09:29 +0800261 logging.info('Set VGA port #%d to mode: %s', port_id, mode)
Tom Wai-Hong Tam26009b12014-11-06 07:02:40 +0800262 self._flows[port_id].SetVgaMode(mode)
263 else:
264 raise DriverError('SetVgaMode only works on VGA port.')
265
266 @_VideoMethod
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800267 def WaitVideoInputStable(self, port_id, timeout=None):
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800268 """Waits the video input stable or timeout.
269
270 Args:
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800271 port_id: The ID of the video input port.
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800272 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 Tam47ebf232014-10-15 08:06:09 +0800278 return self._flows[port_id].WaitVideoInputStable(timeout)
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800279
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 Tamc78b9eb2014-12-18 07:49:51 +0800313 if edid_id > ids.EDID_ID_DEFAULT:
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800314 self._all_edids[edid_id] = None
315 else:
316 raise DriverError('Not a valid edid_id.')
317
Tom Wai-Hong Tamba7451d2014-10-16 06:44:17 +0800318 @_VideoMethod
Tom Wai-Hong Tamae0907d2015-01-14 09:29:50 +0800319 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 Tam47ebf232014-10-15 08:06:09 +0800343 def ReadEdid(self, port_id):
344 """Reads the EDID content of the selected video input on Chameleon.
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800345
346 Args:
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800347 port_id: The ID of the video input port.
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800348
349 Returns:
Tom Wai-Hong Tamc78b9eb2014-12-18 07:49:51 +0800350 A byte array of EDID data, wrapped in a xmlrpclib.Binary object,
351 or None if the EDID is disabled.
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800352 """
Tom Wai-Hong Tamc78b9eb2014-12-18 07:49:51 +0800353 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 Tam0703c542014-05-16 14:13:26 +0800358
Tom Wai-Hong Tamba7451d2014-10-16 06:44:17 +0800359 @_VideoMethod
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800360 def ApplyEdid(self, port_id, edid_id):
361 """Applies the EDID to the selected video input.
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800362
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 Tam47ebf232014-10-15 08:06:09 +0800367 port_id: The ID of the video input port.
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800368 edid_id: The ID of the EDID.
369 """
Tom Wai-Hong Tamc78b9eb2014-12-18 07:49:51 +0800370 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 Tam0703c542014-05-16 14:13:26 +0800379
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800380 def IsPlugged(self, port_id):
381 """Returns true if the port is emulated as plugged.
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800382
383 Args:
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800384 port_id: The ID of the input/output port.
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800385
386 Returns:
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800387 True if the port is emualted as plugged; otherwise, False.
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800388 """
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800389 return self._flows[port_id].IsPlugged()
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800390
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800391 def Plug(self, port_id):
392 """Emualtes plug, like asserting HPD line to high on a video port.
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800393
394 Args:
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800395 port_id: The ID of the input/output port.
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800396 """
Tom Wai-Hong Tam6b0b3882014-12-17 04:09:29 +0800397 logging.info('Plug port #%d', port_id)
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800398 return self._flows[port_id].Plug()
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800399
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800400 def Unplug(self, port_id):
401 """Emulates unplug, like deasserting HPD line to low on a video port.
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800402
403 Args:
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800404 port_id: The ID of the input/output port.
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800405 """
Tom Wai-Hong Tam6b0b3882014-12-17 04:09:29 +0800406 logging.info('Unplug port #%d', port_id)
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800407 return self._flows[port_id].Unplug()
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800408
Tom Wai-Hong Tamba7451d2014-10-16 06:44:17 +0800409 @_VideoMethod
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800410 def FireHpdPulse(self, port_id, deassert_interval_usec,
Yuan3342be72014-07-21 18:12:00 +0800411 assert_interval_usec=None, repeat_count=1,
412 end_level=1):
413 """Fires one or more HPD pulse (low -> high -> low -> ...).
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800414
415 Args:
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800416 port_id: The ID of the video input port.
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800417 deassert_interval_usec: The time in microsecond of the deassert pulse.
418 assert_interval_usec: The time in microsecond of the assert pulse.
Yuan3342be72014-07-21 18:12:00 +0800419 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 Tam0703c542014-05-16 14:13:26 +0800423 """
Yuan3342be72014-07-21 18:12:00 +0800424 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 Tam6b0b3882014-12-17 04:09:29 +0800428 logging.info('Fire HPD pulse on port #%d, ending with %s',
429 port_id, 'high' if end_level else 'low')
Tom Wai-Hong Tam0d128842015-01-14 07:13:45 +0800430 return self._flows[port_id].FireHpdPulse(
431 deassert_interval_usec, assert_interval_usec, repeat_count, end_level)
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800432
Tom Wai-Hong Tamba7451d2014-10-16 06:44:17 +0800433 @_VideoMethod
Hung-ying Tyan8ebc5662014-11-20 18:37:00 +0800434 def FireMixedHpdPulses(self, port_id, widths_msec):
Hung-ying Tyan936c83a2014-09-11 17:28:44 +0800435 """Fires one or more HPD pulses, starting at low, of mixed widths.
436
Hung-ying Tyan8ebc5662014-11-20 18:37:00 +0800437 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 Tyan936c83a2014-09-11 17:28:44 +0800441 The HPD line stops at low if even number of segment widths are specified;
442 otherwise, it stops at high.
443
Hung-ying Tyan8ebc5662014-11-20 18:37:00 +0800444 The method is equivalent to a series of calls to Unplug() and Plug()
445 separated by specified pulse widths.
446
Hung-ying Tyan936c83a2014-09-11 17:28:44 +0800447 Args:
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800448 port_id: The ID of the video input port.
Hung-ying Tyan8ebc5662014-11-20 18:37:00 +0800449 widths_msec: list of pulse segment widths in milli-second.
Hung-ying Tyan936c83a2014-09-11 17:28:44 +0800450 """
Tom Wai-Hong Tam6b0b3882014-12-17 04:09:29 +0800451 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 Tyan8ebc5662014-11-20 18:37:00 +0800453 return self._flows[port_id].FireMixedHpdPulses(widths_msec)
Hung-ying Tyan936c83a2014-09-11 17:28:44 +0800454
Tom Wai-Hong Tamb51a1962014-12-24 02:19:12 +0800455 @_VideoMethod
Tom Wai-Hong Tam50bc4c92015-01-09 02:54:36 +0800456 def SetContentProtection(self, port_id, enabled):
Tom Wai-Hong Tamb51a1962014-12-24 02:19:12 +0800457 """Sets the content protection state on the port.
458
459 Args:
460 port_id: The ID of the video input port.
Tom Wai-Hong Tam50bc4c92015-01-09 02:54:36 +0800461 enabled: True to enable; False to disable.
Tom Wai-Hong Tamb51a1962014-12-24 02:19:12 +0800462 """
Tom Wai-Hong Tam50bc4c92015-01-09 02:54:36 +0800463 logging.info('Set content protection on port #%d: %r', port_id, enabled)
464 self._flows[port_id].SetContentProtection(enabled)
Tom Wai-Hong Tamb51a1962014-12-24 02:19:12 +0800465
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 Tam47ebf232014-10-15 08:06:09 +0800490 def _SelectInput(self, port_id):
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800491 """Selects the input on Chameleon.
492
493 Args:
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800494 port_id: The ID of the input port.
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800495 """
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800496 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 Tam0703c542014-05-16 14:13:26 +0800500
Cheng-Yi Chiang9e62a862014-10-09 19:16:01 +0800501 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 Tamba7451d2014-10-16 06:44:17 +0800512 @_VideoMethod
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800513 def DumpPixels(self, port_id, x=None, y=None, width=None, height=None):
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800514 """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 Tam47ebf232014-10-15 08:06:09 +0800519 port_id: The ID of the video input port.
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800520 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 Tam47ebf232014-10-15 08:06:09 +0800528 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 Tam97bbdd32014-07-24 05:35:45 +0800530 return self.ReadCapturedFrame(self._DEFAULT_FRAME_INDEX)
Tom Wai-Hong Tam9ab344a2014-06-17 03:17:36 +0800531
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800532 def _AutoFillArea(self, port_id, x, y, width, height):
Tom Wai-Hong Tam12609b22014-08-01 01:36:24 +0800533 """Verifies the area argument correctness and fills the default values.
534
Tom Wai-Hong Tam0d490c12014-08-21 09:22:14 +0800535 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 Tam12609b22014-08-01 01:36:24 +0800538 Args:
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800539 port_id: The ID of the video input port.
Tom Wai-Hong Tam12609b22014-08-01 01:36:24 +0800540 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 Tam47ebf232014-10-15 08:06:09 +0800552 return (None, None) + self.DetectResolution(port_id)
Tom Wai-Hong Tam0d490c12014-08-21 09:22:14 +0800553 elif (x, y) == (None, ) * 2 or None not in (x, y, width, height):
Tom Wai-Hong Tam12609b22014-08-01 01:36:24 +0800554 return (x, y, width, height)
555 else:
556 raise DriverError('Some of area arguments are not specified.')
557
Tom Wai-Hong Tamba7451d2014-10-16 06:44:17 +0800558 @_VideoMethod
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800559 def GetMaxFrameLimit(self, port_id, width, height):
Tom Wai-Hong Tamdc3780d2014-08-15 00:45:13 +0800560 """Gets the maximal number of frames which are accommodated in the buffer.
Tom Wai-Hong Tam9ab344a2014-06-17 03:17:36 +0800561
562 It depends on the size of the internal buffer on the board and the
Tom Wai-Hong Tamdc3780d2014-08-15 00:45:13 +0800563 size of area to capture (full screen or cropped area).
Tom Wai-Hong Tam9ab344a2014-06-17 03:17:36 +0800564
565 Args:
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800566 port_id: The ID of the video input port.
Tom Wai-Hong Tamdc3780d2014-08-15 00:45:13 +0800567 width: The width of the area to capture.
568 height: The height of the area to capture.
Tom Wai-Hong Tam9ab344a2014-06-17 03:17:36 +0800569
570 Returns:
571 A number of the frame limit.
572 """
Tom Wai-Hong Tam6bb537f2015-03-19 02:43:08 +0800573 # 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 Tam47ebf232014-10-15 08:06:09 +0800577 return self._flows[port_id].GetMaxFrameLimit(width, height)
Tom Wai-Hong Tam9ab344a2014-06-17 03:17:36 +0800578
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800579 def _PrepareCapturingVideo(self, port_id, x, y, width, height):
580 """Prepares capturing video on the given video input.
Tom Wai-Hong Tam12609b22014-08-01 01:36:24 +0800581
582 Args:
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800583 port_id: The ID of the video input port.
Tom Wai-Hong Tam12609b22014-08-01 01:36:24 +0800584 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 Tam47ebf232014-10-15 08:06:09 +0800589 self._SelectInput(port_id)
590 if not self.IsPlugged(port_id):
Tom Wai-Hong Tam12609b22014-08-01 01:36:24 +0800591 raise DriverError('HPD is unplugged. No signal is expected.')
Tom Wai-Hong Tam12609b22014-08-01 01:36:24 +0800592 self._captured_params = {
Tom Wai-Hong Tam0d128842015-01-14 07:13:45 +0800593 'port_id': port_id,
Tom Wai-Hong Tam6bb537f2015-03-19 02:43:08 +0800594 'max_frame_limit': self._flows[port_id].GetMaxFrameLimit(width, height)
Tom Wai-Hong Tam12609b22014-08-01 01:36:24 +0800595 }
596
Tom Wai-Hong Tamba7451d2014-10-16 06:44:17 +0800597 @_VideoMethod
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800598 def StartCapturingVideo(self, port_id, x=None, y=None, width=None,
Tom Wai-Hong Tam12609b22014-08-01 01:36:24 +0800599 height=None):
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800600 """Starts video capturing continuously on the given video input.
Tom Wai-Hong Tam12609b22014-08-01 01:36:24 +0800601
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 Tam47ebf232014-10-15 08:06:09 +0800614 port_id: The ID of the video input port.
Tom Wai-Hong Tam12609b22014-08-01 01:36:24 +0800615 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 Tam47ebf232014-10-15 08:06:09 +0800620 x, y, width, height = self._AutoFillArea(port_id, x, y, width, height)
621 self._PrepareCapturingVideo(port_id, x, y, width, height)
Tom Wai-Hong Tam6bb537f2015-03-19 02:43:08 +0800622 max_frame_limit = self._captured_params['max_frame_limit']
Tom Wai-Hong Tam6b0b3882014-12-17 04:09:29 +0800623 logging.info('Start capturing video from port #%d', port_id)
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800624 self._flows[port_id].StartDumpingFrames(
Tom Wai-Hong Tam12609b22014-08-01 01:36:24 +0800625 max_frame_limit, x, y, width, height, self._MAX_CAPTURED_FRAME_COUNT)
626
Tom Wai-Hong Tamf12bfa12014-12-18 09:20:39 +0800627 def StopCapturingVideo(self, stop_index=None):
Tom Wai-Hong Tam12609b22014-08-01 01:36:24 +0800628 """Stops video capturing which was started previously.
629
Tom Wai-Hong Tamf12bfa12014-12-18 09:20:39 +0800630 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 Tam12609b22014-08-01 01:36:24 +0800635 Raises:
636 DriverError if the capture period is longer than the capture limitation.
637 """
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800638 port_id = self._captured_params['port_id']
Tom Wai-Hong Tamf12bfa12014-12-18 09:20:39 +0800639 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 Tam47ebf232014-10-15 08:06:09 +0800647 self._flows[port_id].StopDumpingFrames()
Tom Wai-Hong Tam6b0b3882014-12-17 04:09:29 +0800648 logging.info('Stopped capturing video from port #%d', port_id)
Tom Wai-Hong Tam12609b22014-08-01 01:36:24 +0800649 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 Tamba7451d2014-10-16 06:44:17 +0800653 @_VideoMethod
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800654 def CaptureVideo(self, port_id, total_frame, x=None, y=None, width=None,
Tom Wai-Hong Tam97bbdd32014-07-24 05:35:45 +0800655 height=None):
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800656 """Captures the video stream on the given video input to the buffer.
Tom Wai-Hong Tam9ab344a2014-06-17 03:17:36 +0800657
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 Tam47ebf232014-10-15 08:06:09 +0800668 port_id: The ID of the video input port.
Tom Wai-Hong Tam9ab344a2014-06-17 03:17:36 +0800669 total_frame: The total number of frames to capture, should not larger
670 than value of GetMaxFrameLimit.
Tom Wai-Hong Tam97bbdd32014-07-24 05:35:45 +0800671 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 Tam9ab344a2014-06-17 03:17:36 +0800675 """
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800676 x, y, width, height = self._AutoFillArea(port_id, x, y, width, height)
Tom Wai-Hong Tam6b0b3882014-12-17 04:09:29 +0800677 logging.info('Capture video from port #%d', port_id)
Tom Wai-Hong Tam6bb537f2015-03-19 02:43:08 +0800678 self._PrepareCapturingVideo(port_id, x, y, width, height)
679 max_frame_limit = self._captured_params['max_frame_limit']
Tom Wai-Hong Tam9ab344a2014-06-17 03:17:36 +0800680 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 Tam268def62014-07-09 07:45:33 +0800684 # TODO(waihong): Make the timeout value based on the FPS rate.
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800685 self._flows[port_id].DumpFramesToLimit(
Tom Wai-Hong Tamd1266e82014-07-26 07:46:02 +0800686 total_frame, x, y, width, height, self._TIMEOUT_FRAME_DUMP_PROBE)
Tom Wai-Hong Tam97bbdd32014-07-24 05:35:45 +0800687
Tom Wai-Hong Tam12609b22014-08-01 01:36:24 +0800688 def GetCapturedFrameCount(self):
689 """Gets the total count of the captured frames.
Tom Wai-Hong Tam97bbdd32014-07-24 05:35:45 +0800690
Tom Wai-Hong Tam12609b22014-08-01 01:36:24 +0800691 Returns:
692 The number of frames captured.
693 """
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800694 port_id = self._captured_params['port_id']
695 return self._flows[port_id].GetDumpedFrameCount()
Tom Wai-Hong Tamf2b1a202014-06-17 03:04:34 +0800696
697 def GetCapturedResolution(self):
698 """Gets the resolution of the captured frame.
699
Tom Wai-Hong Tam97bbdd32014-07-24 05:35:45 +0800700 If a cropping area is specified on capturing, returns the cropped
701 resolution.
702
Tom Wai-Hong Tamf2b1a202014-06-17 03:04:34 +0800703 Returns:
704 A (width, height) tuple.
705 """
Tom Wai-Hong Tambe82d372015-03-19 07:31:28 +0800706 port_id = self._captured_params['port_id']
707 return self._flows[port_id].GetCapturedResolution()
Tom Wai-Hong Tamf2b1a202014-06-17 03:04:34 +0800708
Tom Wai-Hong Tam97bbdd32014-07-24 05:35:45 +0800709 def ReadCapturedFrame(self, frame_index):
Tom Wai-Hong Tambe82d372015-03-19 07:31:28 +0800710 """Reads the content of the captured frame from the buffer.
Tom Wai-Hong Tamf2b1a202014-06-17 03:04:34 +0800711
712 Args:
713 frame_index: The index of the frame to read.
Tom Wai-Hong Tamf2b1a202014-06-17 03:04:34 +0800714
715 Returns:
716 A byte-array of the pixels, wrapped in a xmlrpclib.Binary object.
717 """
Tom Wai-Hong Tamdd6ce4e2014-12-16 06:34:38 +0800718 port_id = self._captured_params['port_id']
Tom Wai-Hong Tam12609b22014-08-01 01:36:24 +0800719 total_frame = self.GetCapturedFrameCount()
Tom Wai-Hong Tam6bb537f2015-03-19 02:43:08 +0800720 max_frame_limit = self._captured_params['max_frame_limit']
Tom Wai-Hong Tamdd6ce4e2014-12-16 06:34:38 +0800721 # 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 Tambe82d372015-03-19 07:31:28 +0800728 # Use the projected index.
729 frame_index = frame_index % max_frame_limit
730 screen = self._flows[port_id].ReadCapturedFrame(frame_index)
Tom Wai-Hong Tam59d77022014-05-31 00:47:46 +0800731 return xmlrpclib.Binary(screen)
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800732
Tom Wai-Hong Tam3610cce2014-12-13 08:57:27 +0800733 def GetCapturedChecksums(self, start_index=0, stop_index=None):
Tom Wai-Hong Tamd1266e82014-07-26 07:46:02 +0800734 """Gets the list of checksums of the captured frames.
Tom Wai-Hong Tam4f769e02014-07-09 07:54:40 +0800735
736 Args:
Tom Wai-Hong Tam3610cce2014-12-13 08:57:27 +0800737 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 Tam4f769e02014-07-09 07:54:40 +0800740
741 Returns:
Tom Wai-Hong Tamd1266e82014-07-26 07:46:02 +0800742 The list of checksums of frames.
Tom Wai-Hong Tam4f769e02014-07-09 07:54:40 +0800743 """
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800744 port_id = self._captured_params['port_id']
Tom Wai-Hong Tam12609b22014-08-01 01:36:24 +0800745 total_frame = self.GetCapturedFrameCount()
Tom Wai-Hong Tam3610cce2014-12-13 08:57:27 +0800746 if stop_index is None:
747 stop_index = total_frame
Tom Wai-Hong Tam12609b22014-08-01 01:36:24 +0800748 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 Tam47ebf232014-10-15 08:06:09 +0800754 return self._flows[port_id].GetFrameHashes(start_index, stop_index)
Tom Wai-Hong Tam4f769e02014-07-09 07:54:40 +0800755
Tom Wai-Hong Tamba7451d2014-10-16 06:44:17 +0800756 @_VideoMethod
Tom Wai-Hong Tam0d128842015-01-14 07:13:45 +0800757 def ComputePixelChecksum(
758 self, port_id, x=None, y=None, width=None, height=None):
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800759 """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 Tam47ebf232014-10-15 08:06:09 +0800764 port_id: The ID of the video input port.
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800765 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 Tam47ebf232014-10-15 08:06:09 +0800773 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 Tamd1266e82014-07-26 07:46:02 +0800775 return self.GetCapturedChecksums(self._DEFAULT_FRAME_INDEX,
776 self._DEFAULT_FRAME_INDEX + 1)[0]
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800777
Tom Wai-Hong Tamba7451d2014-10-16 06:44:17 +0800778 @_VideoMethod
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800779 def DetectResolution(self, port_id):
780 """Detects the video source resolution.
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800781
782 Args:
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800783 port_id: The ID of the video input port.
Tom Wai-Hong Tam0703c542014-05-16 14:13:26 +0800784
785 Returns:
786 A (width, height) tuple.
787 """
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800788 self._SelectInput(port_id)
Tom Wai-Hong Tam6b0b3882014-12-17 04:09:29 +0800789 resolution = self._flows[port_id].GetResolution()
790 logging.info('Detected resolution on port #%d: %dx%d', port_id, *resolution)
791 return resolution
Cheng-Yi Chiang6048d002014-07-30 17:51:31 +0800792
Cheng-Yi Chiang32cbd452015-02-22 17:54:35 +0800793 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 Chiang9e62a862014-10-09 19:16:01 +0800801 @_AudioMethod(input_only=True)
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800802 def StartCapturingAudio(self, port_id):
Cheng-Yi Chiang6048d002014-07-30 17:51:31 +0800803 """Starts capturing audio.
804
Cheng-Yi Chiangb8b247e2015-01-20 11:41:51 +0800805 Refer to the docstring of StartPlayingEcho about the restriction of
806 capturing and echoing at the same time.
807
Cheng-Yi Chiang6048d002014-07-30 17:51:31 +0800808 Args:
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800809 port_id: The ID of the audio input port.
Cheng-Yi Chiang6048d002014-07-30 17:51:31 +0800810 """
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800811 self._SelectInput(port_id)
Cheng-Yi Chiang9e62a862014-10-09 19:16:01 +0800812 logging.info('Start capturing audio from port #%d', port_id)
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800813 self._flows[port_id].StartCapturingAudio()
Cheng-Yi Chiang6048d002014-07-30 17:51:31 +0800814
Cheng-Yi Chiang9e62a862014-10-09 19:16:01 +0800815 @_AudioMethod(input_only=True)
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800816 def StopCapturingAudio(self, port_id):
Cheng-Yi Chiang6048d002014-07-30 17:51:31 +0800817 """Stops capturing audio and returns recorded audio raw data.
818
819 Args:
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800820 port_id: The ID of the audio input port.
Cheng-Yi Chiang6048d002014-07-30 17:51:31 +0800821
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 Chiangdd9511c2014-10-01 14:45:58 +0800827 Currently, the data format supported is
828 dict(file_type='raw', sample_format='S32_LE', channel=8, rate=48000)
Cheng-Yi Chiang9e62a862014-10-09 19:16:01 +0800829
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 Chiang6048d002014-07-30 17:51:31 +0800835 """
Tom Wai-Hong Tam0d128842015-01-14 07:13:45 +0800836 if self._selected_input != port_id:
Cheng-Yi Chiangdd9511c2014-10-01 14:45:58 +0800837 raise DriverError(
Tom Wai-Hong Tam47ebf232014-10-15 08:06:09 +0800838 'The input is selected to %r not %r', self._selected_input, port_id)
839 data, data_format = self._flows[port_id].StopCapturingAudio()
Cheng-Yi Chiang9e62a862014-10-09 19:16:01 +0800840 logging.info('Stopped capturing audio from port #%d', port_id)
Cheng-Yi Chiang6048d002014-07-30 17:51:31 +0800841 return xmlrpclib.Binary(data), data_format
Cheng-Yi Chiang9e62a862014-10-09 19:16:01 +0800842
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 Chiang9e62a862014-10-09 19:16:01 +0800848
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 Chiang9e62a862014-10-09 19:16:01 +0800862 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 Chiangb8b247e2015-01-20 11:41:51 +0800872 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 Chiang9e62a862014-10-09 19:16:01 +0800894 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 Tam0d128842015-01-14 07:13:45 +0800922 if self._selected_output != port_id:
Cheng-Yi Chiang9e62a862014-10-09 19:16:01 +0800923 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 Chiang32cbd452015-02-22 17:54:35 +0800927
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)