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 5import atexit 6import httplib 7import logging 8import os 9import socket 10import time 11import xmlrpclib 12from contextlib import contextmanager 13 14from PIL import Image 15 16from autotest_lib.client.bin import utils 17from autotest_lib.client.common_lib import error 18from autotest_lib.client.cros.chameleon import audio_board 19from autotest_lib.client.cros.chameleon import edid as edid_lib 20from autotest_lib.client.cros.chameleon import usb_controller 21 22 23CHAMELEON_PORT = 9992 24CHAMELEOND_LOG_REMOTE_PATH = '/var/log/chameleond' 25CHAMELEON_READY_TEST = 'GetSupportedPorts' 26 27 28class ChameleonConnectionError(error.TestError): 29 """Indicates that connecting to Chameleon failed. 30 31 It is fatal to the test unless caught. 32 """ 33 pass 34 35 36class _Method(object): 37 """Class to save the name of the RPC method instead of the real object. 38 39 It keeps the name of the RPC method locally first such that the RPC method 40 can be evaluated to a real object while it is called. Its purpose is to 41 refer to the latest RPC proxy as the original previous-saved RPC proxy may 42 be lost due to reboot. 43 44 The call_server is the method which does refer to the latest RPC proxy. 45 46 This class and the re-connection mechanism in ChameleonConnection is 47 copied from third_party/autotest/files/server/cros/faft/rpc_proxy.py 48 49 """ 50 def __init__(self, call_server, name): 51 """Constructs a _Method. 52 53 @param call_server: the call_server method 54 @param name: the method name or instance name provided by the 55 remote server 56 57 """ 58 self.__call_server = call_server 59 self._name = name 60 61 62 def __getattr__(self, name): 63 """Support a nested method. 64 65 For example, proxy.system.listMethods() would need to use this method 66 to get system and then to get listMethods. 67 68 @param name: the method name or instance name provided by the 69 remote server 70 71 @return: a callable _Method object. 72 73 """ 74 return _Method(self.__call_server, "%s.%s" % (self._name, name)) 75 76 77 def __call__(self, *args, **dargs): 78 """The call method of the object. 79 80 @param args: arguments for the remote method. 81 @param kwargs: keyword arguments for the remote method. 82 83 @return: the result returned by the remote method. 84 85 """ 86 return self.__call_server(self._name, *args, **dargs) 87 88 89class ChameleonConnection(object): 90 """ChameleonConnection abstracts the network connection to the board. 91 92 When a chameleon board is rebooted, a xmlrpc call would incur a 93 socket error. To fix the error, a client has to reconnect to the server. 94 ChameleonConnection is a wrapper of chameleond proxy created by 95 xmlrpclib.ServerProxy(). ChameleonConnection has the capability to 96 automatically reconnect to the server when such socket error occurs. 97 The nice feature is that the auto re-connection is performed inside this 98 wrapper and is transparent to the caller. 99 100 Note: 101 1. When running chameleon autotests in lab machines, it is 102 ChameleonConnection._create_server_proxy() that is invoked. 103 2. When running chameleon autotests in local chroot, it is 104 rpc_server_tracker.xmlrpc_connect() in server/hosts/chameleon_host.py 105 that is invoked. 106 107 ChameleonBoard and ChameleonPort use it for accessing Chameleon RPC. 108 109 """ 110 111 def __init__(self, hostname, port=CHAMELEON_PORT, proxy_generator=None, 112 ready_test_name=CHAMELEON_READY_TEST): 113 """Constructs a ChameleonConnection. 114 115 @param hostname: Hostname the chameleond process is running. 116 @param port: Port number the chameleond process is listening on. 117 @param proxy_generator: a function to generate server proxy. 118 @param ready_test_name: run this method on the remote server ot test 119 if the server is connected correctly. 120 121 @raise ChameleonConnectionError if connection failed. 122 """ 123 self._hostname = hostname 124 self._port = port 125 126 # Note: it is difficult to put the lambda function as the default 127 # value of the proxy_generator argument. In that case, the binding 128 # of arguments (hostname and port) would be delayed until run time 129 # which requires to pass an instance as an argument to labmda. 130 # That becomes cumbersome since server/hosts/chameleon_host.py 131 # would also pass a lambda without argument to instantiate this object. 132 # Use the labmda function as follows would bind the needed arguments 133 # immediately which is much simpler. 134 self._proxy_generator = proxy_generator or self._create_server_proxy 135 136 self._ready_test_name = ready_test_name 137 self.chameleond_proxy = None 138 139 140 def _create_server_proxy(self): 141 """Creates the chameleond server proxy. 142 143 @param hostname: Hostname the chameleond process is running. 144 @param port: Port number the chameleond process is listening on. 145 146 @return ServerProxy object to chameleond. 147 148 @raise ChameleonConnectionError if connection failed. 149 150 """ 151 remote = 'http://%s:%s' % (self._hostname, self._port) 152 chameleond_proxy = xmlrpclib.ServerProxy(remote, allow_none=True) 153 logging.info('ChameleonConnection._create_server_proxy() called') 154 # Call a RPC to test. 155 try: 156 getattr(chameleond_proxy, self._ready_test_name)() 157 except (socket.error, 158 xmlrpclib.ProtocolError, 159 httplib.BadStatusLine) as e: 160 raise ChameleonConnectionError(e) 161 return chameleond_proxy 162 163 164 def _reconnect(self): 165 """Reconnect to chameleond.""" 166 self.chameleond_proxy = self._proxy_generator() 167 168 169 def __call_server(self, name, *args, **kwargs): 170 """Bind the name to the chameleond proxy and execute the method. 171 172 @param name: the method name or instance name provided by the 173 remote server. 174 @param args: arguments for the remote method. 175 @param kwargs: keyword arguments for the remote method. 176 177 @return: the result returned by the remote method. 178 179 """ 180 try: 181 return getattr(self.chameleond_proxy, name)(*args, **kwargs) 182 except (AttributeError, socket.error): 183 # Reconnect and invoke the method again. 184 logging.info('Reconnecting chameleond proxy: %s', name) 185 self._reconnect() 186 return getattr(self.chameleond_proxy, name)(*args, **kwargs) 187 188 189 def __getattr__(self, name): 190 """Get the callable _Method object. 191 192 @param name: the method name or instance name provided by the 193 remote server 194 195 @return: a callable _Method object. 196 197 """ 198 return _Method(self.__call_server, name) 199 200 201class ChameleonBoard(object): 202 """ChameleonBoard is an abstraction of a Chameleon board. 203 204 A Chameleond RPC proxy is passed to the construction such that it can 205 use this proxy to control the Chameleon board. 206 207 User can use host to access utilities that are not provided by 208 Chameleond XMLRPC server, e.g. send_file and get_file, which are provided by 209 ssh_host.SSHHost, which is the base class of ChameleonHost. 210 211 """ 212 213 def __init__(self, chameleon_connection, chameleon_host=None): 214 """Construct a ChameleonBoard. 215 216 @param chameleon_connection: ChameleonConnection object. 217 @param chameleon_host: ChameleonHost object. None if this ChameleonBoard 218 is not created by a ChameleonHost. 219 """ 220 self.host = chameleon_host 221 self._output_log_file = None 222 self._chameleond_proxy = chameleon_connection 223 self._usb_ctrl = usb_controller.USBController(chameleon_connection) 224 if self._chameleond_proxy.HasAudioBoard(): 225 self._audio_board = audio_board.AudioBoard(chameleon_connection) 226 else: 227 self._audio_board = None 228 logging.info('There is no audio board on this Chameleon.') 229 230 231 def reset(self): 232 """Resets Chameleon board.""" 233 self._chameleond_proxy.Reset() 234 235 236 def setup_and_reset(self, output_dir=None): 237 """Setup and reset Chameleon board. 238 239 @param output_dir: Setup the output directory. 240 None for just reset the board. 241 """ 242 if output_dir and self.host is not None: 243 logging.info('setup_and_reset: dir %s, chameleon host %s', 244 output_dir, self.host.hostname) 245 log_dir = os.path.join(output_dir, 'chameleond', self.host.hostname) 246 # Only clear the chameleon board log and register get log callback 247 # when we first create the log_dir. 248 if not os.path.exists(log_dir): 249 # remove old log. 250 self.host.run('>%s' % CHAMELEOND_LOG_REMOTE_PATH) 251 os.makedirs(log_dir) 252 self._output_log_file = os.path.join(log_dir, 'log') 253 atexit.register(self._get_log) 254 self.reset() 255 256 257 def reboot(self): 258 """Reboots Chameleon board.""" 259 self._chameleond_proxy.Reboot() 260 261 262 def _get_log(self): 263 """Get log from chameleon. It will be registered by atexit. 264 265 It's a private method. We will setup output_dir before using this 266 method. 267 """ 268 self.host.get_file(CHAMELEOND_LOG_REMOTE_PATH, self._output_log_file) 269 270 271 def get_all_ports(self): 272 """Gets all the ports on Chameleon board which are connected. 273 274 @return: A list of ChameleonPort objects. 275 """ 276 ports = self._chameleond_proxy.ProbePorts() 277 return [ChameleonPort(self._chameleond_proxy, port) for port in ports] 278 279 280 def get_all_inputs(self): 281 """Gets all the input ports on Chameleon board which are connected. 282 283 @return: A list of ChameleonPort objects. 284 """ 285 ports = self._chameleond_proxy.ProbeInputs() 286 return [ChameleonPort(self._chameleond_proxy, port) for port in ports] 287 288 289 def get_all_outputs(self): 290 """Gets all the output ports on Chameleon board which are connected. 291 292 @return: A list of ChameleonPort objects. 293 """ 294 ports = self._chameleond_proxy.ProbeOutputs() 295 return [ChameleonPort(self._chameleond_proxy, port) for port in ports] 296 297 298 def get_label(self): 299 """Gets the label which indicates the display connection. 300 301 @return: A string of the label, like 'hdmi', 'dp_hdmi', etc. 302 """ 303 connectors = [] 304 for port in self._chameleond_proxy.ProbeInputs(): 305 if self._chameleond_proxy.HasVideoSupport(port): 306 connector = self._chameleond_proxy.GetConnectorType(port).lower() 307 connectors.append(connector) 308 # Eliminate duplicated ports. It simplifies the labels of dual-port 309 # devices, i.e. dp_dp categorized into dp. 310 return '_'.join(sorted(set(connectors))) 311 312 313 def get_audio_board(self): 314 """Gets the audio board on Chameleon. 315 316 @return: An AudioBoard object. 317 """ 318 return self._audio_board 319 320 321 def get_usb_controller(self): 322 """Gets the USB controller on Chameleon. 323 324 @return: A USBController object. 325 """ 326 return self._usb_ctrl 327 328 329 def get_bluetooh_hid_mouse(self): 330 """Gets the emulated bluetooth hid mouse on Chameleon. 331 332 @return: A BluetoothHIDMouseFlow object. 333 """ 334 return self._chameleond_proxy.bluetooth_mouse 335 336 337 def get_avsync_probe(self): 338 """Gets the avsync probe device on Chameleon. 339 340 @return: An AVSyncProbeFlow object. 341 """ 342 return self._chameleond_proxy.avsync_probe 343 344 345 def get_mac_address(self): 346 """Gets the MAC address of Chameleon. 347 348 @return: A string for MAC address. 349 """ 350 return self._chameleond_proxy.GetMacAddress() 351 352 353class ChameleonPort(object): 354 """ChameleonPort is an abstraction of a general port of a Chameleon board. 355 356 It only contains some common methods shared with audio and video ports. 357 358 A Chameleond RPC proxy and an port_id are passed to the construction. 359 The port_id is the unique identity to the port. 360 """ 361 362 def __init__(self, chameleond_proxy, port_id): 363 """Construct a ChameleonPort. 364 365 @param chameleond_proxy: Chameleond RPC proxy object. 366 @param port_id: The ID of the input port. 367 """ 368 self.chameleond_proxy = chameleond_proxy 369 self.port_id = port_id 370 371 372 def get_connector_id(self): 373 """Returns the connector ID. 374 375 @return: A number of connector ID. 376 """ 377 return self.port_id 378 379 380 def get_connector_type(self): 381 """Returns the human readable string for the connector type. 382 383 @return: A string, like "VGA", "DVI", "HDMI", or "DP". 384 """ 385 return self.chameleond_proxy.GetConnectorType(self.port_id) 386 387 388 def has_audio_support(self): 389 """Returns if the input has audio support. 390 391 @return: True if the input has audio support; otherwise, False. 392 """ 393 return self.chameleond_proxy.HasAudioSupport(self.port_id) 394 395 396 def has_video_support(self): 397 """Returns if the input has video support. 398 399 @return: True if the input has video support; otherwise, False. 400 """ 401 return self.chameleond_proxy.HasVideoSupport(self.port_id) 402 403 404 def plug(self): 405 """Asserts HPD line to high, emulating plug.""" 406 logging.info('Plug Chameleon port %d', self.port_id) 407 self.chameleond_proxy.Plug(self.port_id) 408 409 410 def unplug(self): 411 """Deasserts HPD line to low, emulating unplug.""" 412 logging.info('Unplug Chameleon port %d', self.port_id) 413 self.chameleond_proxy.Unplug(self.port_id) 414 415 416 def set_plug(self, plug_status): 417 """Sets plug/unplug by plug_status. 418 419 @param plug_status: True to plug; False to unplug. 420 """ 421 if plug_status: 422 self.plug() 423 else: 424 self.unplug() 425 426 427 @property 428 def plugged(self): 429 """ 430 @returns True if this port is plugged to Chameleon, False otherwise. 431 432 """ 433 return self.chameleond_proxy.IsPlugged(self.port_id) 434 435 436class ChameleonVideoInput(ChameleonPort): 437 """ChameleonVideoInput is an abstraction of a video input port. 438 439 It contains some special methods to control a video input. 440 """ 441 442 _DUT_STABILIZE_TIME = 3 443 _DURATION_UNPLUG_FOR_EDID = 5 444 _TIMEOUT_VIDEO_STABLE_PROBE = 10 445 _EDID_ID_DISABLE = -1 446 _FRAME_RATE = 60 447 448 def __init__(self, chameleon_port): 449 """Construct a ChameleonVideoInput. 450 451 @param chameleon_port: A general ChameleonPort object. 452 """ 453 self.chameleond_proxy = chameleon_port.chameleond_proxy 454 self.port_id = chameleon_port.port_id 455 self._original_edid = None 456 457 458 def wait_video_input_stable(self, timeout=None): 459 """Waits the video input stable or timeout. 460 461 @param timeout: The time period to wait for. 462 463 @return: True if the video input becomes stable within the timeout 464 period; otherwise, False. 465 """ 466 is_input_stable = self.chameleond_proxy.WaitVideoInputStable( 467 self.port_id, timeout) 468 469 # If video input of Chameleon has been stable, wait for DUT software 470 # layer to be stable as well to make sure all the configurations have 471 # been propagated before proceeding. 472 if is_input_stable: 473 logging.info('Video input has been stable. Waiting for the DUT' 474 ' to be stable...') 475 time.sleep(self._DUT_STABILIZE_TIME) 476 return is_input_stable 477 478 479 def read_edid(self): 480 """Reads the EDID. 481 482 @return: An Edid object or NO_EDID. 483 """ 484 edid_binary = self.chameleond_proxy.ReadEdid(self.port_id) 485 if edid_binary is None: 486 return edid_lib.NO_EDID 487 # Read EDID without verify. It may be made corrupted as intended 488 # for the test purpose. 489 return edid_lib.Edid(edid_binary.data, skip_verify=True) 490 491 492 def apply_edid(self, edid): 493 """Applies the given EDID. 494 495 @param edid: An Edid object or NO_EDID. 496 """ 497 if edid is edid_lib.NO_EDID: 498 self.chameleond_proxy.ApplyEdid(self.port_id, self._EDID_ID_DISABLE) 499 else: 500 edid_binary = xmlrpclib.Binary(edid.data) 501 edid_id = self.chameleond_proxy.CreateEdid(edid_binary) 502 self.chameleond_proxy.ApplyEdid(self.port_id, edid_id) 503 self.chameleond_proxy.DestroyEdid(edid_id) 504 505 506 def set_edid_from_file(self, filename): 507 """Sets EDID from a file. 508 509 The method is similar to set_edid but reads EDID from a file. 510 511 @param filename: path to EDID file. 512 """ 513 self.set_edid(edid_lib.Edid.from_file(filename)) 514 515 516 def set_edid(self, edid): 517 """The complete flow of setting EDID. 518 519 Unplugs the port if needed, sets EDID, plugs back if it was plugged. 520 The original EDID is stored so user can call restore_edid after this 521 call. 522 523 @param edid: An Edid object. 524 """ 525 plugged = self.plugged 526 if plugged: 527 self.unplug() 528 529 self._original_edid = self.read_edid() 530 531 logging.info('Apply EDID on port %d', self.port_id) 532 self.apply_edid(edid) 533 534 if plugged: 535 time.sleep(self._DURATION_UNPLUG_FOR_EDID) 536 self.plug() 537 self.wait_video_input_stable(self._TIMEOUT_VIDEO_STABLE_PROBE) 538 539 540 def restore_edid(self): 541 """Restores original EDID stored when set_edid was called.""" 542 current_edid = self.read_edid() 543 if (self._original_edid and 544 self._original_edid.data != current_edid.data): 545 logging.info('Restore the original EDID.') 546 self.apply_edid(self._original_edid) 547 548 549 @contextmanager 550 def use_edid(self, edid): 551 """Uses the given EDID in a with statement. 552 553 It sets the EDID up in the beginning and restores to the original 554 EDID in the end. This function is expected to be used in a with 555 statement, like the following: 556 557 with chameleon_port.use_edid(edid): 558 do_some_test_on(chameleon_port) 559 560 @param edid: An EDID object. 561 """ 562 # Set the EDID up in the beginning. 563 self.set_edid(edid) 564 565 try: 566 # Yeild to execute the with statement. 567 yield 568 finally: 569 # Restore the original EDID in the end. 570 self.restore_edid() 571 572 573 def use_edid_file(self, filename): 574 """Uses the given EDID file in a with statement. 575 576 It sets the EDID up in the beginning and restores to the original 577 EDID in the end. This function is expected to be used in a with 578 statement, like the following: 579 580 with chameleon_port.use_edid_file(filename): 581 do_some_test_on(chameleon_port) 582 583 @param filename: A path to the EDID file. 584 """ 585 return self.use_edid(edid_lib.Edid.from_file(filename)) 586 587 588 def fire_hpd_pulse(self, deassert_interval_usec, assert_interval_usec=None, 589 repeat_count=1, end_level=1): 590 591 """Fires one or more HPD pulse (low -> high -> low -> ...). 592 593 @param deassert_interval_usec: The time in microsecond of the 594 deassert pulse. 595 @param assert_interval_usec: The time in microsecond of the 596 assert pulse. If None, then use the same value as 597 deassert_interval_usec. 598 @param repeat_count: The count of HPD pulses to fire. 599 @param end_level: HPD ends with 0 for LOW (unplugged) or 1 for 600 HIGH (plugged). 601 """ 602 self.chameleond_proxy.FireHpdPulse( 603 self.port_id, deassert_interval_usec, 604 assert_interval_usec, repeat_count, int(bool(end_level))) 605 606 607 def fire_mixed_hpd_pulses(self, widths): 608 """Fires one or more HPD pulses, starting at low, of mixed widths. 609 610 One must specify a list of segment widths in the widths argument where 611 widths[0] is the width of the first low segment, widths[1] is that of 612 the first high segment, widths[2] is that of the second low segment... 613 etc. The HPD line stops at low if even number of segment widths are 614 specified; otherwise, it stops at high. 615 616 @param widths: list of pulse segment widths in usec. 617 """ 618 self.chameleond_proxy.FireMixedHpdPulses(self.port_id, widths) 619 620 621 def capture_screen(self): 622 """Captures Chameleon framebuffer. 623 624 @return An Image object. 625 """ 626 return Image.fromstring( 627 'RGB', 628 self.get_resolution(), 629 self.chameleond_proxy.DumpPixels(self.port_id).data) 630 631 632 def get_resolution(self): 633 """Gets the source resolution. 634 635 @return: A (width, height) tuple. 636 """ 637 # The return value of RPC is converted to a list. Convert it back to 638 # a tuple. 639 return tuple(self.chameleond_proxy.DetectResolution(self.port_id)) 640 641 642 def set_content_protection(self, enable): 643 """Sets the content protection state on the port. 644 645 @param enable: True to enable; False to disable. 646 """ 647 self.chameleond_proxy.SetContentProtection(self.port_id, enable) 648 649 650 def is_content_protection_enabled(self): 651 """Returns True if the content protection is enabled on the port. 652 653 @return: True if the content protection is enabled; otherwise, False. 654 """ 655 return self.chameleond_proxy.IsContentProtectionEnabled(self.port_id) 656 657 658 def is_video_input_encrypted(self): 659 """Returns True if the video input on the port is encrypted. 660 661 @return: True if the video input is encrypted; otherwise, False. 662 """ 663 return self.chameleond_proxy.IsVideoInputEncrypted(self.port_id) 664 665 666 def start_monitoring_audio_video_capturing_delay(self): 667 """Starts an audio/video synchronization utility.""" 668 self.chameleond_proxy.StartMonitoringAudioVideoCapturingDelay() 669 670 671 def get_audio_video_capturing_delay(self): 672 """Gets the time interval between the first audio/video cpatured data. 673 674 @return: A floating points indicating the time interval between the 675 first audio/video data captured. If the result is negative, 676 then the first video data is earlier, otherwise the first 677 audio data is earlier. 678 """ 679 return self.chameleond_proxy.GetAudioVideoCapturingDelay() 680 681 682 def start_capturing_video(self, box=None): 683 """ 684 Captures video frames. Asynchronous, returns immediately. 685 686 @param box: int tuple, (x, y, width, height) pixel coordinates. 687 Defines the rectangular boundary within which to capture. 688 """ 689 690 if box is None: 691 self.chameleond_proxy.StartCapturingVideo(self.port_id) 692 else: 693 self.chameleond_proxy.StartCapturingVideo(self.port_id, *box) 694 695 696 def stop_capturing_video(self): 697 """ 698 Stops the ongoing video frame capturing. 699 700 """ 701 self.chameleond_proxy.StopCapturingVideo() 702 703 704 def get_captured_frame_count(self): 705 """ 706 @return: int, the number of frames that have been captured. 707 708 """ 709 return self.chameleond_proxy.GetCapturedFrameCount() 710 711 712 def read_captured_frame(self, index): 713 """ 714 @param index: int, index of the desired captured frame. 715 @return: xmlrpclib.Binary object containing a byte-array of the pixels. 716 717 """ 718 719 frame = self.chameleond_proxy.ReadCapturedFrame(index) 720 return Image.fromstring('RGB', 721 self.get_captured_resolution(), 722 frame.data) 723 724 725 def get_captured_checksums(self, start_index=0, stop_index=None): 726 """ 727 @param start_index: int, index of the frame to start with. 728 @param stop_index: int, index of the frame (excluded) to stop at. 729 @return: a list of checksums of frames captured. 730 731 """ 732 return self.chameleond_proxy.GetCapturedChecksums(start_index, 733 stop_index) 734 735 736 def get_captured_fps_list(self, time_to_start=0, total_period=None): 737 """ 738 @param time_to_start: time in second, support floating number, only 739 measure the period starting at this time. 740 If negative, it is the time before stop, e.g. 741 -2 meaning 2 seconds before stop. 742 @param total_period: time in second, integer, the total measuring 743 period. If not given, use the maximum time 744 (integer) to the end. 745 @return: a list of fps numbers, or [-1] if any error. 746 747 """ 748 checksums = self.get_captured_checksums() 749 750 frame_to_start = int(round(time_to_start * self._FRAME_RATE)) 751 if total_period is None: 752 # The default is the maximum time (integer) to the end. 753 total_period = (len(checksums) - frame_to_start) / self._FRAME_RATE 754 frame_to_stop = frame_to_start + total_period * self._FRAME_RATE 755 756 if frame_to_start >= len(checksums) or frame_to_stop >= len(checksums): 757 logging.error('The given time interval is out-of-range.') 758 return [-1] 759 760 # Only pick the checksum we are interested. 761 checksums = checksums[frame_to_start:frame_to_stop] 762 763 # Count the unique checksums per second, i.e. FPS 764 logging.debug('Output the fps info below:') 765 fps_list = [] 766 for i in xrange(0, len(checksums), self._FRAME_RATE): 767 unique_count = 0 768 debug_str = '' 769 for j in xrange(i, i + self._FRAME_RATE): 770 if j == 0 or checksums[j] != checksums[j - 1]: 771 unique_count += 1 772 debug_str += '*' 773 else: 774 debug_str += '.' 775 fps_list.append(unique_count) 776 logging.debug('%2dfps %s', unique_count, debug_str) 777 778 return fps_list 779 780 781 def search_fps_pattern(self, pattern_diff_frame, pattern_window=None, 782 time_to_start=0): 783 """Search the captured frames and return the time where FPS is greater 784 than given FPS pattern. 785 786 A FPS pattern is described as how many different frames in a sliding 787 window. For example, 5 differnt frames in a window of 60 frames. 788 789 @param pattern_diff_frame: number of different frames for the pattern. 790 @param pattern_window: number of frames for the sliding window. Default 791 is 1 second. 792 @param time_to_start: time in second, support floating number, 793 start to search from the given time. 794 @return: the time matching the pattern. -1.0 if not found. 795 796 """ 797 if pattern_window is None: 798 pattern_window = self._FRAME_RATE 799 800 checksums = self.get_captured_checksums() 801 802 frame_to_start = int(round(time_to_start * self._FRAME_RATE)) 803 first_checksum = checksums[frame_to_start] 804 805 for i in xrange(frame_to_start + 1, len(checksums) - pattern_window): 806 unique_count = 0 807 for j in xrange(i, i + pattern_window): 808 if j == 0 or checksums[j] != checksums[j - 1]: 809 unique_count += 1 810 if unique_count >= pattern_diff_frame: 811 return float(i) / self._FRAME_RATE 812 813 return -1.0 814 815 816 def get_captured_resolution(self): 817 """ 818 @return: (width, height) tuple, the resolution of captured frames. 819 820 """ 821 return self.chameleond_proxy.GetCapturedResolution() 822 823 824 825class ChameleonAudioInput(ChameleonPort): 826 """ChameleonAudioInput is an abstraction of an audio input port. 827 828 It contains some special methods to control an audio input. 829 """ 830 831 def __init__(self, chameleon_port): 832 """Construct a ChameleonAudioInput. 833 834 @param chameleon_port: A general ChameleonPort object. 835 """ 836 self.chameleond_proxy = chameleon_port.chameleond_proxy 837 self.port_id = chameleon_port.port_id 838 839 840 def start_capturing_audio(self): 841 """Starts capturing audio.""" 842 return self.chameleond_proxy.StartCapturingAudio(self.port_id) 843 844 845 def stop_capturing_audio(self): 846 """Stops capturing audio. 847 848 Returns: 849 A tuple (remote_path, format). 850 remote_path: The captured file path on Chameleon. 851 format: A dict containing: 852 file_type: 'raw' or 'wav'. 853 sample_format: 'S32_LE' for 32-bit signed integer in little-endian. 854 Refer to aplay manpage for other formats. 855 channel: channel number. 856 rate: sampling rate. 857 """ 858 remote_path, data_format = self.chameleond_proxy.StopCapturingAudio( 859 self.port_id) 860 return remote_path, data_format 861 862 863class ChameleonAudioOutput(ChameleonPort): 864 """ChameleonAudioOutput is an abstraction of an audio output port. 865 866 It contains some special methods to control an audio output. 867 """ 868 869 def __init__(self, chameleon_port): 870 """Construct a ChameleonAudioOutput. 871 872 @param chameleon_port: A general ChameleonPort object. 873 """ 874 self.chameleond_proxy = chameleon_port.chameleond_proxy 875 self.port_id = chameleon_port.port_id 876 877 878 def start_playing_audio(self, path, data_format): 879 """Starts playing audio. 880 881 @param path: The path to the file to play on Chameleon. 882 @param data_format: A dict containing data format. Currently Chameleon 883 only accepts data format: 884 dict(file_type='raw', sample_format='S32_LE', 885 channel=8, rate=48000). 886 887 """ 888 self.chameleond_proxy.StartPlayingAudio(self.port_id, path, data_format) 889 890 891 def stop_playing_audio(self): 892 """Stops capturing audio.""" 893 self.chameleond_proxy.StopPlayingAudio(self.port_id) 894 895 896def make_chameleon_hostname(dut_hostname): 897 """Given a DUT's hostname, returns the hostname of its Chameleon. 898 899 @param dut_hostname: Hostname of a DUT. 900 901 @return Hostname of the DUT's Chameleon. 902 """ 903 host_parts = dut_hostname.split('.') 904 host_parts[0] = host_parts[0] + '-chameleon' 905 return '.'.join(host_parts) 906 907 908def create_chameleon_board(dut_hostname, args): 909 """Given either DUT's hostname or argments, creates a ChameleonBoard object. 910 911 If the DUT's hostname is in the lab zone, it connects to the Chameleon by 912 append the hostname with '-chameleon' suffix. If not, checks if the args 913 contains the key-value pair 'chameleon_host=IP'. 914 915 @param dut_hostname: Hostname of a DUT. 916 @param args: A string of arguments passed from the command line. 917 918 @return A ChameleonBoard object. 919 920 @raise ChameleonConnectionError if unknown hostname. 921 """ 922 connection = None 923 hostname = make_chameleon_hostname(dut_hostname) 924 if utils.host_is_in_lab_zone(hostname): 925 connection = ChameleonConnection(hostname) 926 else: 927 args_dict = utils.args_to_dict(args) 928 hostname = args_dict.get('chameleon_host', None) 929 port = args_dict.get('chameleon_port', CHAMELEON_PORT) 930 if hostname: 931 connection = ChameleonConnection(hostname, port) 932 else: 933 raise ChameleonConnectionError('No chameleon_host is given in args') 934 935 return ChameleonBoard(connection) 936